unit memscan;
{
This unit will hold the class object used to control scanning
The old scanning routines will be moved out of cefuncproc and made object oriented into this class
Special care should be taken to add multithreaded scanning routines
}

interface

uses windows,sysutils, classes,ComCtrls,dialogs, cefuncproc,
     newkernelhandler, math, SyncObjs
{$ifndef standalonetrainerwithassemblerandaobscanner}
     , windows7taskbar,SaveFirstScan, firstscanhandler, autoassembler, symbolhandler
{$endif}
;


type TCheckRoutine=function(newvalue,oldvalue: pointer):boolean of object;
type TStoreResultRoutine=procedure(address: dword; oldvalue: pointer) of object;
type TFlushRoutine=procedure of object;



type
  TMemScan=class;
  TScanController=class;
  TScanner=class;

  Tscanfilewriter=class(tthread)
  {
  workerthread:
  This class is used because it is more efficient to write only one file at a
  time. It will aquire a critical section when writing a file to acomplish that

  also, not that it matters much, since you eventually have to wait for the
  write anyhow, a double buffered result list so it can keep scanning while the
  list is being saved.
  }
  private
    scancontroller: TScanController;
    scanner: TScanner;
    addressfile: TFilestream;
    memoryfile: TFilestream;
//    cAddressFile,cMemoryFile: TCompressionStream;

    addressbuffer: pointer;
    memorybuffer: pointer;
    addressSize: dword;
    memorySize: dword;



    datawritten: tevent; //event that is set when the thread has finished writing
    dataavailable:tevent; //event that is set when there is a buffer to save
  public
    writeError: boolean; //gets set if an exception happened
    errorString: string; //exception description
    procedure execute; override;
    procedure writeresults(addressbuffer,memorybuffer: pointer; addressSize,memorySize: dword); //writes the results of address and memory
    procedure flush;
    constructor create(scanner: TScanner; scancontroller:TScanController; addressfile,memoryfile:TFileStream);
    destructor destroy; override;
  end;


  TScanner=class(tthread)
  {
    The scanner class will scan a specified range of memory
  }
  private
    CheckRoutine: TCheckRoutine;
    StoreResultRoutine: TStoreResultRoutine;
    FlushRoutine: TFlushRoutine; //pointer to routine used to flush the buffer, generic, string, etc...
    scanWriter: Tscanfilewriter;
    {$ifndef standalonetrainerwithassemblerandaobscanner} 
    firstscanhandler: TFirstscanhandler;
    {$endif}

    customprologue: procedure; stdcall; //customscan call before scan starts
    customepilogue: procedure; stdcall; //customscan call after scan ends

    found :dword;

    value,value2: int64;
    svalue,svalue2: single;
    dvalue,dvalue2: double;
    minsvalue,maxsvalue: single;
    mindvalue,maxdvalue: double;
    floataccuracy: integer; //number of digits after the decimal seperator

    CurrentAddressBuffer: pointer;
    CurrentFoundBuffer: pointer; //generic buffer that points to where the memory can be found
    SecondaryFoundBuffer: pointer;   //current gets swapped with these ones after a write
    SecondaryAddressBuffer: pointer;

    //binary scan
    bitmask: int64; //contains the bit string where * is replaced with a 0
    andmask: int64; //contains the bit string where 1 and 0 is 1 and * is 0
    binaryresults: array [0..7] of boolean; //after a byte is checked this contains which startbits match

    //array of bytes
    abs_arraylength: integer; //optimization so no need to call length()
    abs_arraytofind: TBytes;

    //all
    typesmatch: array [vtByte..vtDouble] of boolean;  //will get set depending if that type matches the current address or not

    //check routines:
    function ByteExact(newvalue,oldvalue: pointer): boolean;
    function ByteBetween(newvalue,oldvalue: pointer): boolean;
    function ByteBiggerThan(newvalue,oldvalue: pointer): boolean;
    function ByteSmallerThan(newvalue,oldvalue: pointer): boolean;
    function ByteIncreasedValue(newvalue,oldvalue: pointer): boolean;
    function ByteIncreasedValueBy(newvalue,oldvalue: pointer): boolean;
    function ByteIncreasedValueByPercentage(newvalue,oldvalue: pointer): boolean;
    function ByteDecreasedValue(newvalue,oldvalue: pointer): boolean;
    function ByteDecreasedValueBy(newvalue,oldvalue: pointer): boolean;
    function ByteDecreasedValueByPercentage(newvalue,oldvalue: pointer): boolean;    
    function ByteChanged(newvalue,oldvalue: pointer): boolean;
    function ByteUnChanged(newvalue,oldvalue: pointer): boolean;

    function WordExact(newvalue,oldvalue: pointer): boolean;
    function WordBetween(newvalue,oldvalue: pointer): boolean;
    function WordBiggerThan(newvalue,oldvalue: pointer): boolean;
    function WordSmallerThan(newvalue,oldvalue: pointer): boolean;
    function WordIncreasedValue(newvalue,oldvalue: pointer): boolean;
    function WordIncreasedValueBy(newvalue,oldvalue: pointer): boolean;
    function WordIncreasedValueByPercentage(newvalue,oldvalue: pointer): boolean;
    function WordDecreasedValue(newvalue,oldvalue: pointer): boolean;
    function WordDecreasedValueBy(newvalue,oldvalue: pointer): boolean;
    function WordDecreasedValueByPercentage(newvalue,oldvalue: pointer): boolean;
    function WordChanged(newvalue,oldvalue: pointer): boolean;
    function WordUnChanged(newvalue,oldvalue: pointer): boolean;

    function DWordExact(newvalue,oldvalue: pointer): boolean;
    function DWordBetween(newvalue,oldvalue: pointer): boolean;
    function DWordBiggerThan(newvalue,oldvalue: pointer): boolean;
    function DWordSmallerThan(newvalue,oldvalue: pointer): boolean;
    function DWordIncreasedValue(newvalue,oldvalue: pointer): boolean;
    function DWordIncreasedValueBy(newvalue,oldvalue: pointer): boolean;
    function DWordIncreasedValueByPercentage(newvalue,oldvalue: pointer): boolean;
    function DWordDecreasedValue(newvalue,oldvalue: pointer): boolean;
    function DWordDecreasedValueBy(newvalue,oldvalue: pointer): boolean;
    function DWordDecreasedValueByPercentage(newvalue,oldvalue: pointer):boolean;
    function DwordChanged(newvalue,oldvalue: pointer): boolean;
    function DwordUnChanged(newvalue,oldvalue: pointer): boolean;


    function QWordExact(newvalue,oldvalue: pointer): boolean;
    function QWordBetween(newvalue,oldvalue: pointer): boolean;
    function QWordBiggerThan(newvalue,oldvalue: pointer): boolean;
    function QWordSmallerThan(newvalue,oldvalue: pointer): boolean;
    function QWordIncreasedValue(newvalue,oldvalue: pointer): boolean;
    function QWordIncreasedValueBy(newvalue,oldvalue: pointer): boolean;
    function QWordIncreasedValueByPercentage(newvalue,oldvalue: pointer): boolean;
    function QWordDecreasedValue(newvalue,oldvalue: pointer): boolean;
    function QWordDecreasedValueBy(newvalue,oldvalue: pointer): boolean;
    function QWordDecreasedValueByPercentage(newvalue,oldvalue: pointer): boolean;
    function QWordChanged(newvalue,oldvalue: pointer): boolean;
    function QwordUnChanged(newvalue,oldvalue: pointer): boolean;

    function SingleExact(newvalue,oldvalue: pointer): boolean;
    function SingleBetween(newvalue,oldvalue: pointer): boolean;
    function SingleBiggerThan(newvalue,oldvalue: pointer): boolean;
    function SingleSmallerThan(newvalue,oldvalue: pointer): boolean;
    function SingleIncreasedValue(newvalue,oldvalue: pointer): boolean;
    function SingleIncreasedValueBy(newvalue,oldvalue: pointer): boolean;
    function SingleIncreasedValueByPercentage(newvalue,oldvalue: pointer): boolean;
    function SingleDecreasedValue(newvalue,oldvalue: pointer): boolean;
    function SingleDecreasedValueBy(newvalue,oldvalue: pointer): boolean;
    function SingleDecreasedValueByPercentage(newvalue,oldvalue: pointer): boolean;    
    function SingleChanged(newvalue,oldvalue: pointer): boolean;
    function singleUnChanged(newvalue,oldvalue: pointer): boolean;

    function DoubleExact(newvalue,oldvalue: pointer): boolean;
    function DoubleBetween(newvalue,oldvalue: pointer): boolean;
    function DoubleBiggerThan(newvalue,oldvalue: pointer): boolean;
    function DoubleSmallerThan(newvalue,oldvalue: pointer): boolean;
    function DoubleIncreasedValue(newvalue,oldvalue: pointer): boolean;
    function DoubleIncreasedValueBy(newvalue,oldvalue: pointer): boolean;
    function DoubleIncreasedValueByPercentage(newvalue,oldvalue: pointer): boolean;
    function DoubleDecreasedValue(newvalue,oldvalue: pointer): boolean;
    function DoubleDecreasedValueBy(newvalue,oldvalue: pointer): boolean;
    function DoubleDecreasedValueByPercentage(newvalue,oldvalue: pointer): boolean;    
    function DoubleChanged(newvalue,oldvalue: pointer): boolean;
    function DoubleUnChanged(newvalue,oldvalue: pointer): boolean;

    function AllExact(newvalue,oldvalue: pointer):boolean; //check byte,word,dword,qword,single and float
    function AllBetween(newvalue,oldvalue: pointer): boolean;
    function AllBiggerThan(newvalue,oldvalue: pointer): boolean;
    function AllSmallerThan(newvalue,oldvalue: pointer): boolean;
    function AllIncreasedValue(newvalue,oldvalue: pointer): boolean;
    function AllIncreasedValueBy(newvalue,oldvalue: pointer): boolean;
    function AllIncreasedValueByPercentage(newvalue,oldvalue: pointer): boolean;
    function AllDecreasedValue(newvalue,oldvalue: pointer): boolean;
    function AllDecreasedValueBy(newvalue,oldvalue: pointer): boolean;
    function AllDecreasedValueByPercentage(newvalue,oldvalue: pointer): boolean;
    function AllChanged(newvalue,oldvalue: pointer): boolean;
    function AllUnchanged(newvalue,oldvalue: pointer): boolean;

    //following types only have exact: Array of byte, binary and string
    function ArrayOfByteExact(newvalue,oldvalue: pointer):boolean;
    function BinaryExact(newvalue,oldvalue: pointer):boolean;
    function CaseSensitiveAnsiStringExact(newvalue,oldvalue: pointer):boolean;
    function CaseInsensitiveAnsiStringExact(newvalue,oldvalue: pointer):boolean;
    function CaseSensitiveUnicodeStringExact(newvalue,oldvalue: pointer):boolean;
    function CaseInsensitiveUnicodeStringExact(newvalue,oldvalue: pointer):boolean;



    //save macthing address routines:
    procedure GenericSaveResult(address: dword; oldvalue: pointer); //only use as last resort : Call to copymemory just to store one entry
    procedure allSaveResult(address: dword; oldvalue: pointer);
    procedure binarySaveResult(address: dword; oldvalue: pointer);
    procedure arrayOfByteSaveResult(address: dword; oldvalue: pointer);
    procedure ByteSaveResult(address: dword; oldvalue: pointer);
    procedure WordSaveResult(address: dword; oldvalue: pointer);
    procedure DWordSaveResult(address: dword; oldvalue: pointer);    
    procedure QWordSaveResult(address: dword; oldvalue: pointer);
    procedure SingleSaveResult(address: dword; oldvalue: pointer);
    procedure DoubleSaveResult(address: dword; oldvalue: pointer);    

    //flush routines
    procedure genericFlush; //generic routine for flushing the buffer
    procedure stringFlush; //don't save the memory
    procedure binaryFlush; //don't save memory AND use foundaddressb for results
    procedure allFlush;


    procedure configurescanroutine; //parses scanvalue1,2 according to the VariableType and scanoptions
    procedure FirstScanmem(base:dword; buffer: pointer; size: integer); //routine that gets a buffer and saves all the results it finds in the buffer (For firstscan)
    procedure FirstNextScanmem(base:dword; buffer,oldbuffer: pointer; size: integer);

    procedure nextnextscanmemall(addresslist: pointer; oldmemory: pointer; chunksize: integer);
    procedure nextnextscanmembinary(addresslist: pointer; chunksize: integer);
    procedure nextnextscanmem(addresslist: pointer; oldmemory: pointer; chunksize: integer);

    procedure firstscan; //copy the given range to the memory region
    procedure firstnextscan; //routine used when the results list contains nothing but a indicator a unknown scan was done
    procedure nextnextscan; //routine used when the results list contains addresses

  public
    OwningScanController: TScanController;
    Addressfile: TFilestream; //CheatEngineDir+'Addresses'+ThreadID.TMP'
    MemoryFile: TFileStream;  //CheatEngineDir+'Memory'+ThreadID.TMP'
    Addressfilename: string;
    MemoryFilename: string;

    percentage: boolean;
    roundingtype: TRoundingtype;
    hexadecimal: boolean;
    binaryStringAsDecimal: boolean;
    readonly: boolean;
    fastscan: boolean;
    unicode: boolean;
    caseSensitive: boolean;
    fastscanalignsize: integer;
    variablesize: integer;
    scanvalue1,scanvalue2: string;
    widescanvalue1: widestring;
    scanOption: TScanOption;
    variableType: TVariableType;
    scanType: TScanType; //defines if it's a firstscan or next scan. (newscan is ignored)
    useNextNextscan: boolean; //determines to use the nextNextScan or firstNextScan

    customscanscript: tstrings; //holds the info for the custom type, script for AA, script for CScript, 2 lines for dll: dllname+function
    customscantype: TCustomScanType;
    customscanAllocArray: TCEAllocArray;




    //thread controlling variables:
    isdone: boolean; //will get set to true when the thread finishes normally
    haserror: boolean;
    errorstring: string;

    //region related scans:
    //startregion and stopregion
    _startregion: integer;
    _stopregion: integer;
    maxregionsize: dword; //max size of buffer to be allocated when not unknown scan

    //recreated memory region list for this specific range, can be used to see which regions where only half read
    memRegions: TMemoryregions;
    memRegionPos: integer;

    startaddress: dword; //specific start for this this thread
    stopaddress: dword; //specific stop for this thread, if not fastscan and another thread continue from here, may add some overlopping bytes

    //exact address scans:
    startentry: integer; //index in the address list
    stopentry: integer; //"   "

    //general:
    scanned: dword; //total memory/addresses scanned by this routine
    totalfound: dword;

    OnlyOne: boolean;
    AddressFound: dword;
    scannernr: integer;

    procedure execute; override;
    constructor create(suspended: boolean);
    destructor destroy; override;
  end;

  TScanController=class(tthread)
  {
    The ScanController will configure the scanners and wait till they are done, mainly a idle thread
  }
  private
    threadcount: integer;
    addressFile: TFileStream;
    memoryFile: TFileStream;
    savescannerresults: boolean; //tells the epilogue to save the results to addressfile and memoryfile
    isdoneEvent: TEvent; //gets set when the scan has finished

    procedure updategui;
    procedure errorpopup;
    procedure firstScan;
    procedure nextScan;
    procedure firstnextscan; //rotine used when the results list contains nothing but a indicator a unknown scan was done
    procedure nextnextscan; //routine used when the results list contains addresses
    procedure fillVariableAndFastScanAlignSize; //fills in the variablesize and fastscanalignsizevariables, used for firstscan and firstnextscan
  public
    OwningMemScan: TMemScan;
    
    resultsaveCS: tcriticalsection; //critical section the scanfilewriter objects use to determine if there is another scanthread writing
    scannersCS: TCriticalSection; //Critican section used aty the end of the scancontroller thread, when the scanners are being destroyed
    scanners: array of tscanner;

    totalAddresses: dword; //how many addresses it will have to scan

    percentage: boolean;
    roundingtype: TRoundingType;
    hexadecimal: boolean;
    binaryStringAsDecimal: boolean;
    readonly: boolean;
    fastscan: boolean;
    unicode: boolean;
    casesensitive: boolean;
    fastscanalignsize: integer;
    variablesize: integer;
    scanvalue1,scanvalue2: string;
    startaddress: dword; //start for the whole scan
    stopaddress: dword; //stop of the whole scan
    scanOption: TScanOption;
    variableType: TVariableType;
    scanType: TScanType; //defines if it's a firstscan or next scan. (newscan is ignored)

    customscanscript: tstrings; //holds the info for the custom type, script for AA, script for CScript, 2 lines for dll: dllname+function
    customscantype: TCustomScanType;    

    //memregion info
    memregion: TMemoryregions;  //scanners have access to this, but make sure to NOT WRITE it
    memRegionPos: Integer;



    //thread controlling variables:
    isdone: boolean; //will get set to true when the thread finishes normally
    haserror: boolean;
    errorstring: string;

    //messages
    notifywindow: thandle;
    notifymessage: integer;

    //OnlyOne vars
    OnlyOne: boolean;
    FoundSomething: Boolean;
    AddressFound: Dword;

    procedure execute; override;
    constructor create(suspended: boolean);
    destructor destroy; override;
  end;

  TMemScan=class
  {
    Configures the gui and related objects and launch TScanner objects with those objects
  }
  private
    previousMemoryBuffer: pointer;
    scanController: TScanController; //thread that configures the scanner threads and wait till they are done
    {$ifndef standalonetrainerwithassemblerandaobscanner}
    SaveFirstScanThread: TSaveFirstScanThread; //thread that will save the results of the first scan that can be used for "same as first scan" scans
    {$endif}
    memRegion: TMemoryRegions;  //after a scan the contents of controller gets copied to here
    memRegionPos: integer;

    progressbar: TProgressBar;
    notifywindow: thandle;
    notifymessage: integer;

    currentVariableType: TVariableType;
    found: uint64;

    //string stuff:
    stringUnicode: boolean;
    stringLength:  integer;

    //binary stuff:
    binaryLength:  integer;

    //custom:
    customvariablesize: integer;

    //array stuff:
    arrayLength:   integer;


    FLastScanType: TScanType;
  public
    onlyOne: boolean;
    function GetProgress(var totaladdressestoscan:dword; var currentlyscanned: dword):integer;
    function GetErrorString: string;
    function GetFoundCount: uint64;
    function Getbinarysize: int64; //returns the number of bits of the current type
    function Getcustomvariablesize: integer;
    function GetOnlyOneResult(var address: dword):boolean;
    procedure TerminateScan(forceTermination: boolean);
    procedure newscan; //will clean up the memory and files
    procedure firstscan(scanOption: TScanOption; VariableType: TVariableType; roundingtype: TRoundingType; scanvalue1, scanvalue2: string; startaddress,stopaddress: dword; fastscan,readonly,hexadecimal,binaryStringAsDecimal,unicode,casesensitive,percentage: boolean; customscanscript: tstrings; customscantype: TCustomScanType);
    procedure NextScan(scanOption: TScanOption; roundingtype: TRoundingType; scanvalue1, scanvalue2: string; startaddress,stopaddress: dword; fastscan,readonly,hexadecimal,binaryStringAsDecimal,unicode,casesensitive,percentage: boolean; customscanscript: tstrings; customscantype: TCustomScanType); //next scan, determine what kind of scan and give to firstnextscan/nextnextscan
    procedure waittilldone;

    procedure setScanDoneCallback(notifywindow: thandle; notifymessage: integer);
    constructor create(progressbar: TProgressbar);
    destructor destroy; override;

    property LastScanType: TScanType read FLastScanType;
  end;



implementation

uses StrUtils;

var foundaddress: byte;
    foundaddressb: byte;


//===============Local functions================//
function getBytecountArrayOfByteString(st: string): integer;
var bytes: tbytes;
begin
  ConvertStringToBytes(st,true,bytes);
  result:=length(bytes);
end;


function getBytecountBinaryString(st:string; scanvalueisdecimal: boolean): integer;
var value: dword;
    i: integer;
begin
  if scanvalueisdecimal then //first convert do binarystring
    st:=inttobin(strtoint(st));


  result:=0;
  for i:=1 to length(st) do
  begin
    case st[i] of
      '0','1','?','*': inc(result);
      ' ',#8: ; //ignore
      else raise exception.Create(st[i]+' is not a valid character inside a binary string');
    end;
  end;

  if result=0 then raise exception.Create('Invalid binary notation');
  if (result mod 8=0) then
    result:=1+result div 8
  else
    result:=2+(result div 8);

end;



//==================TScanner===================//


//-----------=====Scanner check routines=====--------------//

function TScanner.AllExact(newvalue,oldvalue: pointer):boolean;
var i: TVariableType;
begin
  typesmatch[vtByte]:=typesmatch[vtByte] and ByteExact(newvalue,oldvalue); //oldvalue=nil, but give it anyhow
  typesmatch[vtWord]:=typesmatch[vtWord] and WordExact(newvalue,oldvalue);
  typesmatch[vtDword]:=typesmatch[vtDword] and DwordExact(newvalue,oldvalue);
  typesmatch[vtQword]:=typesmatch[vtQword] and qwordExact(newvalue,oldvalue);
  typesmatch[vtSingle]:=typesmatch[vtSingle] and singleExact(newvalue,oldvalue);
  typesmatch[vtDouble]:=typesmatch[vtDouble] and doubleExact(newvalue,oldvalue);

  result:=false;
  for i:=vtbyte to vtdouble do
    if typesmatch[i] then
    begin
      result:=true;
      exit;
    end;
end;

function TScanner.AllBetween(newvalue,oldvalue: pointer):boolean;
var i: TVariableType;
begin
  typesmatch[vtByte]:=typesmatch[vtByte] and ByteBetween(newvalue,oldvalue);
  typesmatch[vtWord]:=typesmatch[vtWord] and WordBetween(newvalue,oldvalue);
  typesmatch[vtDword]:=typesmatch[vtDword] and DwordBetween(newvalue,oldvalue);
  typesmatch[vtQword]:=typesmatch[vtQword] and qwordBetween(newvalue,oldvalue);
  typesmatch[vtSingle]:=typesmatch[vtSingle] and singleBetween(newvalue,oldvalue);
  typesmatch[vtDouble]:=typesmatch[vtDouble] and doubleBetween(newvalue,oldvalue);

  result:=false;
  for i:=vtbyte to vtdouble do
    if typesmatch[i] then
    begin
      result:=true;
      exit;
    end;
end;

function TScanner.AllBiggerThan(newvalue,oldvalue: pointer):boolean;
var i: TVariableType;
begin
  typesmatch[vtByte]:=typesmatch[vtByte] and ByteBiggerThan(newvalue,oldvalue);
  typesmatch[vtWord]:=typesmatch[vtWord] and WordBiggerThan(newvalue,oldvalue);
  typesmatch[vtDword]:=typesmatch[vtDword] and DwordBiggerThan(newvalue,oldvalue);
  typesmatch[vtQword]:=typesmatch[vtQword] and qwordBiggerThan(newvalue,oldvalue);
  typesmatch[vtSingle]:=typesmatch[vtSingle] and singleBiggerThan(newvalue,oldvalue);
  typesmatch[vtDouble]:=typesmatch[vtDouble] and doubleBiggerThan(newvalue,oldvalue);

  result:=false;
  for i:=vtbyte to vtdouble do
    if typesmatch[i] then
    begin
      result:=true;
      exit;
    end;
end;

function TScanner.AllSmallerThan(newvalue,oldvalue: pointer):boolean;
var i: TVariableType;
begin
  typesmatch[vtByte]:=typesmatch[vtByte] and ByteSmallerThan(newvalue,oldvalue);
  typesmatch[vtWord]:=typesmatch[vtWord] and WordSmallerThan(newvalue,oldvalue);
  typesmatch[vtDword]:=typesmatch[vtDword] and DwordSmallerThan(newvalue,oldvalue);
  typesmatch[vtQword]:=typesmatch[vtQword] and qwordSmallerThan(newvalue,oldvalue);
  typesmatch[vtSingle]:=typesmatch[vtSingle] and singleSmallerThan(newvalue,oldvalue);
  typesmatch[vtDouble]:=typesmatch[vtDouble] and doubleSmallerThan(newvalue,oldvalue);

  result:=false;
  for i:=vtbyte to vtdouble do
    if typesmatch[i] then
    begin
      result:=true;
      exit;
    end;
end;

function TScanner.AllIncreasedValue(newvalue,oldvalue: pointer):boolean;
var i: TVariableType;
begin
  typesmatch[vtByte]:=typesmatch[vtByte] and ByteIncreasedValue(newvalue,oldvalue);
  typesmatch[vtWord]:=typesmatch[vtWord] and WordIncreasedValue(newvalue,oldvalue);
  typesmatch[vtDword]:=typesmatch[vtDword] and DwordIncreasedValue(newvalue,oldvalue);
  typesmatch[vtQword]:=typesmatch[vtQword] and qwordIncreasedValue(newvalue,oldvalue);
  typesmatch[vtSingle]:=typesmatch[vtSingle] and singleIncreasedValue(newvalue,oldvalue);
  typesmatch[vtDouble]:=typesmatch[vtDouble] and doubleIncreasedValue(newvalue,oldvalue);

  result:=false;
  for i:=vtbyte to vtdouble do
    if typesmatch[i] then
    begin
      result:=true;
      exit;
    end;
end;

function TScanner.AllIncreasedValueBy(newvalue,oldvalue: pointer):boolean;
var i: TVariableType;
begin
  typesmatch[vtByte]:=typesmatch[vtByte] and ByteIncreasedValueBy(newvalue,oldvalue);
  typesmatch[vtWord]:=typesmatch[vtWord] and WordIncreasedValueBy(newvalue,oldvalue);
  typesmatch[vtDword]:=typesmatch[vtDword] and DwordIncreasedValueBy(newvalue,oldvalue);
  typesmatch[vtQword]:=typesmatch[vtQword] and qwordIncreasedValueBy(newvalue,oldvalue);
  typesmatch[vtSingle]:=typesmatch[vtSingle] and singleIncreasedValueBy(newvalue,oldvalue);
  typesmatch[vtDouble]:=typesmatch[vtDouble] and doubleIncreasedValueBy(newvalue,oldvalue);

  result:=false;
  for i:=vtbyte to vtdouble do
    if typesmatch[i] then
    begin
      result:=true;
      exit;
    end;
end;

function TScanner.AllIncreasedValueByPercentage(newvalue,oldvalue: pointer):boolean;
var i: TVariableType;
begin
  typesmatch[vtByte]:=typesmatch[vtByte] and ByteIncreasedValueByPercentage(newvalue,oldvalue);
  typesmatch[vtWord]:=typesmatch[vtWord] and WordIncreasedValueByPercentage(newvalue,oldvalue);
  typesmatch[vtDword]:=typesmatch[vtDword] and DwordIncreasedValueByPercentage(newvalue,oldvalue);
  typesmatch[vtQword]:=typesmatch[vtQword] and qwordIncreasedValueByPercentage(newvalue,oldvalue);
  typesmatch[vtSingle]:=typesmatch[vtSingle] and singleIncreasedValueByPercentage(newvalue,oldvalue);
  typesmatch[vtDouble]:=typesmatch[vtDouble] and doubleIncreasedValueByPercentage(newvalue,oldvalue);

  result:=false;
  for i:=vtbyte to vtdouble do
    if typesmatch[i] then
    begin
      result:=true;
      exit;
    end;
end;


function TScanner.AllDecreasedValue(newvalue,oldvalue: pointer):boolean;
var i: TVariableType;
begin
  typesmatch[vtByte]:=typesmatch[vtByte] and ByteDecreasedValue(newvalue,oldvalue);
  typesmatch[vtWord]:=typesmatch[vtWord] and WordDecreasedValue(newvalue,oldvalue);
  typesmatch[vtDword]:=typesmatch[vtDword] and DwordDecreasedValue(newvalue,oldvalue);
  typesmatch[vtQword]:=typesmatch[vtQword] and qwordDecreasedValue(newvalue,oldvalue);
  typesmatch[vtSingle]:=typesmatch[vtSingle] and singleDecreasedValue(newvalue,oldvalue);
  typesmatch[vtDouble]:=typesmatch[vtDouble] and doubleDecreasedValue(newvalue,oldvalue);

  result:=false;
  for i:=vtbyte to vtdouble do
    if typesmatch[i] then
    begin
      result:=true;
      exit;
    end;
end;

function TScanner.AllDecreasedValueBy(newvalue,oldvalue: pointer):boolean;
var i: TVariableType;
begin
  typesmatch[vtByte]:=typesmatch[vtByte] and ByteDecreasedValueBy(newvalue,oldvalue);
  typesmatch[vtWord]:=typesmatch[vtWord] and WordDecreasedValueBy(newvalue,oldvalue);
  typesmatch[vtDword]:=typesmatch[vtDword] and DwordDecreasedValueBy(newvalue,oldvalue);

  typesmatch[vtQword]:=typesmatch[vtQword] and qwordDecreasedValueBy(newvalue,oldvalue);
  typesmatch[vtSingle]:=typesmatch[vtSingle] and singleDecreasedValueBy(newvalue,oldvalue);
  typesmatch[vtDouble]:=typesmatch[vtDouble] and doubleDecreasedValueBy(newvalue,oldvalue);

  result:=false;
  for i:=vtbyte to vtdouble do
    if typesmatch[i] then
    begin
      result:=true;
      exit;
    end;
end;

function TScanner.AllDecreasedValueByPercentage(newvalue,oldvalue: pointer):boolean;
var i: TVariableType;
begin
  typesmatch[vtByte]:=typesmatch[vtByte] and ByteDecreasedValueByPercentage(newvalue,oldvalue);
  typesmatch[vtWord]:=typesmatch[vtWord] and WordDecreasedValueByPercentage(newvalue,oldvalue);
  typesmatch[vtDword]:=typesmatch[vtDword] and DwordDecreasedValueByPercentage(newvalue,oldvalue);

  typesmatch[vtQword]:=typesmatch[vtQword] and qwordDecreasedValueByPercentage(newvalue,oldvalue);
  typesmatch[vtSingle]:=typesmatch[vtSingle] and singleDecreasedValueByPercentage(newvalue,oldvalue);
  typesmatch[vtDouble]:=typesmatch[vtDouble] and doubleDecreasedValueByPercentage(newvalue,oldvalue);

  result:=false;
  for i:=vtbyte to vtdouble do
    if typesmatch[i] then
    begin
      result:=true;
      exit;
    end;
end;

function TScanner.Allchanged(newvalue,oldvalue: pointer):boolean;
var i: TVariableType;
begin
  typesmatch[vtByte]:=typesmatch[vtByte] and ByteChanged(newvalue,oldvalue);
  typesmatch[vtWord]:=typesmatch[vtWord] and WordChanged(newvalue,oldvalue);
  typesmatch[vtDword]:=typesmatch[vtDword] and DwordChanged(newvalue,oldvalue);
  typesmatch[vtQword]:=typesmatch[vtQword] and qwordChanged(newvalue,oldvalue);
  typesmatch[vtSingle]:=typesmatch[vtSingle] and singleChanged(newvalue,oldvalue);
  typesmatch[vtDouble]:=typesmatch[vtDouble] and doubleChanged(newvalue,oldvalue);

  result:=false;
  for i:=vtbyte to vtdouble do
    if typesmatch[i] then
    begin
      result:=true;
      exit;
    end;
end;

function TScanner.AllUnchanged(newvalue,oldvalue: pointer):boolean;
var i: TVariableType;
begin
  typesmatch[vtByte]:=typesmatch[vtByte] and ByteUnchanged(newvalue,oldvalue);
  typesmatch[vtWord]:=typesmatch[vtWord] and WordUnchanged(newvalue,oldvalue);
  typesmatch[vtDword]:=typesmatch[vtDword] and DwordUnchanged(newvalue,oldvalue);
  typesmatch[vtQword]:=typesmatch[vtQword] and qwordUnchanged(newvalue,oldvalue);
  typesmatch[vtSingle]:=typesmatch[vtSingle] and singleUnchanged(newvalue,oldvalue);
  typesmatch[vtDouble]:=typesmatch[vtDouble] and doubleUnchanged(newvalue,oldvalue);

  result:=false;
  for i:=vtbyte to vtdouble do
    if typesmatch[i] then
    begin
      result:=true;
      exit;
    end;
end;

function TScanner.CaseSensitiveAnsiStringExact(newvalue,oldvalue: pointer):boolean;
var i: integer;
begin
  result:=false;
  for i:=1 to length(scanvalue1) do
    if scanvalue1[i]<>(pchar(newvalue)[i-1]) then exit;

  result:=true;
end;

function TScanner.CaseInsensitiveAnsiStringExact(newvalue,oldvalue: pointer):boolean;
var i: integer;
begin
  //scanvalue1 has already been converted to uppercase in config
  result:=false;
  for i:=1 to length(scanvalue1) do
  begin
    if pchar(newvalue)[(i-1)] in ['a'..'z'] then //change to uppercase
      dec(pbytearray(newvalue)[(i-1)],$20);

    if scanvalue1[i]<>(pchar(newvalue)[i-1]) then exit;
  end;

  result:=true; //it got here, so a match
end;

function TScanner.CaseSensitiveUnicodeStringExact(newvalue,oldvalue: pointer):boolean;
var i: integer;
begin
  result:=false;

  for i:=1 to length(scanvalue1) do
    if widescanvalue1[i]<>(pwidechar(newvalue)[i-1]) then exit;

  result:=true;
end;

function TScanner.CaseInsensitiveUnicodeStringExact(newvalue,oldvalue: pointer):boolean;
var i: integer;
    scanvaluellength: integer;
    pwidescanvalue: pchar;
begin
  result:=false;
  i:=0;
  scanvaluellength:=length(scanvalue1)*sizeof(widechar);
  pwidescanvalue:=@widescanvalue1[1];

  for i:=1 to length(scanvalue1) do
  begin
    if pchar(newvalue)[(i-1)*sizeof(wchar)] in ['a'..'z'] then //change to uppercase
      dec(pbytearray(newvalue)[(i-1)*sizeof(wchar)],$20);

    if widescanvalue1[i]<>(pwidechar(newvalue)[i-1]) then exit;
  end;

  result:=true; //it got here, so a match
end;


function TScanner.ArrayOfByteExact(newvalue,oldvalue: pointer):boolean;
var i: integer;
begin
  for i:=0 to abs_arraylength-1 do
    if (abs_arraytofind[i]<>-1) and (pbytearray(newvalue)[i]<>abs_arraytofind[i]) then
    begin
      result:=false; //no match
      exit;
    end;

  result:=true; //still here, so a match
end;

function TScanner.BinaryExact(newvalue,oldvalue: pointer):boolean;
var i: integer;
begin
  for i:=0 to 7 do
    binaryresults[i]:=((puint64(newvalue)^ shr i) and andmask)=bitmask;

  //no need for a result here, for binary that isn't checked (special case)
  result:=true; //let the store result routine deal with it
end;

//byte:
function TScanner.ByteExact(newvalue,oldvalue: pointer):boolean;
begin
  result:=pbyte(newvalue)^=byte(value);
end;

function TScanner.ByteBetween(newvalue,oldvalue: pointer):boolean;
begin
  result:=(pbyte(newvalue)^>=byte(value)) and (pbyte(newvalue)^<=byte(value2));
end;

function TScanner.ByteBiggerThan(newvalue,oldvalue: pointer):boolean;
begin
  result:=pbyte(newvalue)^>byte(value);
end;

function TScanner.ByteSmallerThan(newvalue,oldvalue: pointer):boolean;
begin
  result:=pbyte(newvalue)^<byte(value);
end;

function TScanner.ByteIncreasedValue(newvalue,oldvalue: pointer):boolean;
begin
  result:=pbyte(newvalue)^>pbyte(oldvalue)^;
end;

function TScanner.ByteIncreasedValueBy(newvalue,oldvalue: pointer):boolean;
begin
  result:=pbyte(newvalue)^=pbyte(oldvalue)^+byte(value);
end;

function TScanner.ByteIncreasedValueByPercentage(newvalue,oldvalue: pointer):boolean;
begin
  result:=pbyte(newvalue)^>trunc(pbyte(oldvalue)^+pbyte(oldvalue)^*svalue);
end;

function TScanner.ByteDecreasedValue(newvalue,oldvalue: pointer):boolean;
begin
  result:=pbyte(newvalue)^<pbyte(oldvalue)^;
end;

function TScanner.ByteDecreasedValueBy(newvalue,oldvalue: pointer):boolean;
begin
  result:=pbyte(newvalue)^=pbyte(oldvalue)^-byte(value);
end;

function TScanner.ByteDecreasedValueByPercentage(newvalue,oldvalue: pointer):boolean;
begin
  result:=pbyte(newvalue)^<trunc(pbyte(oldvalue)^-pbyte(oldvalue)^*svalue);
end;

function TScanner.ByteChanged(newvalue,oldvalue: pointer):boolean;
begin
  result:=pbyte(newvalue)^<>pbyte(oldvalue)^;
end;

function TScanner.ByteUnchanged(newvalue,oldvalue: pointer):boolean; //also used for same as first
begin
  result:=pbyte(newvalue)^=pbyte(oldvalue)^;
end;


//word:
function TScanner.WordExact(newvalue,oldvalue: pointer): boolean;
begin
  result:=pword(newvalue)^=word(value);
end;

function TScanner.WordBetween(newvalue,oldvalue: pointer):boolean;
begin
  result:=(pword(newvalue)^>=word(value)) and (pword(newvalue)^<=word(value2));
end;

function TScanner.WordBiggerThan(newvalue,oldvalue: pointer):boolean;
begin
  result:=pword(newvalue)^>word(value);
end;

function TScanner.WordSmallerThan(newvalue,oldvalue: pointer):boolean;
begin
  result:=pword(newvalue)^<word(value);
end;

function TScanner.WordIncreasedValue(newvalue,oldvalue: pointer):boolean;
begin
  result:=pword(newvalue)^>pword(oldvalue)^;
end;

function TScanner.WordIncreasedValueBy(newvalue,oldvalue: pointer):boolean;
begin
  result:=pword(newvalue)^=pword(oldvalue)^+word(value);
end;

function TScanner.WordIncreasedValueByPercentage(newvalue,oldvalue: pointer):boolean;
begin
  result:=pword(newvalue)^>trunc(pword(oldvalue)^+pword(oldvalue)^*svalue);
end;

function TScanner.WordDecreasedValue(newvalue,oldvalue: pointer):boolean;
begin
  result:=pword(newvalue)^<pword(oldvalue)^;
end;

function TScanner.WordDecreasedValueBy(newvalue,oldvalue: pointer):boolean;
begin
  result:=pword(newvalue)^=pword(oldvalue)^-word(value);
end;

function TScanner.WordDecreasedValueByPercentage(newvalue,oldvalue: pointer):boolean;
begin
  result:=pword(newvalue)^<trunc(pword(oldvalue)^-pword(oldvalue)^*svalue);
end;

function TScanner.WordChanged(newvalue,oldvalue: pointer):boolean;
begin
  result:=pword(newvalue)^<>pword(oldvalue)^;
end;

function TScanner.WordUnchanged(newvalue,oldvalue: pointer):boolean;
begin
  result:=pword(newvalue)^=pword(oldvalue)^;
end;


//dword:
function TScanner.DWordExact(newvalue,oldvalue: pointer): boolean;
begin
  result:=pdword(newvalue)^=dword(value);
end;

function TScanner.DWordBetween(newvalue,oldvalue: pointer):boolean;
begin
  result:=(pdword(newvalue)^>=dword(value)) and (pdword(newvalue)^<=dword(value2));
end;

function TScanner.DWordBiggerThan(newvalue,oldvalue: pointer):boolean;
begin
  result:=pdword(newvalue)^>dword(value);
end;

function TScanner.DWordSmallerThan(newvalue,oldvalue: pointer):boolean;
begin
  result:=pdword(newvalue)^<dword(value);
end;

function TScanner.DWordIncreasedValue(newvalue,oldvalue: pointer):boolean;
begin
  result:=pdword(newvalue)^>pdword(oldvalue)^;
end;

function TScanner.DWordIncreasedValueBy(newvalue,oldvalue: pointer):boolean;
begin
  result:=pdword(newvalue)^=pdword(oldvalue)^+dword(value);
end;

function TScanner.DWordIncreasedValueByPercentage(newvalue,oldvalue: pointer):boolean;
begin
  result:=pdword(newvalue)^>trunc(pdword(oldvalue)^+pdword(oldvalue)^*svalue);
end;

function TScanner.DWordDecreasedValue(newvalue,oldvalue: pointer):boolean;
begin
  result:=pdword(newvalue)^<pdword(oldvalue)^;
end;

function TScanner.DWordDecreasedValueBy(newvalue,oldvalue: pointer):boolean;
begin
  result:=pdword(newvalue)^=pdword(oldvalue)^-dword(value);
end;

function TScanner.DWordDecreasedValueByPercentage(newvalue,oldvalue: pointer):boolean;
begin
  result:=pdword(newvalue)^<trunc(pdword(oldvalue)^-pdword(oldvalue)^*svalue);
end;

function TScanner.DWordChanged(newvalue,oldvalue: pointer):boolean;
begin
  result:=pdword(newvalue)^<>pdword(oldvalue)^;
end;

function TScanner.DWordUnchanged(newvalue,oldvalue: pointer):boolean;
begin
  result:=pdword(newvalue)^=pdword(oldvalue)^;
end;


//int64
function TScanner.QWordExact(newvalue,oldvalue: pointer): boolean;
begin
  result:=puint64(newvalue)^=uint64(value);
end;

function TScanner.QWordBetween(newvalue,oldvalue: pointer):boolean;
begin
  result:=(puint64(newvalue)^>=uint64(value)) and (puint64(newvalue)^<=uint64(value2));
end;

function TScanner.QWordBiggerThan(newvalue,oldvalue: pointer):boolean;
begin
  result:=puint64(newvalue)^>uint64(value);
end;

function TScanner.QWordSmallerThan(newvalue,oldvalue: pointer):boolean;
begin
  result:=puint64(newvalue)^<uint64(value);
end;

function TScanner.QWordIncreasedValue(newvalue,oldvalue: pointer):boolean;
begin
  result:=puint64(newvalue)^>puint64(oldvalue)^;
end;

function TScanner.QWordIncreasedValueBy(newvalue,oldvalue: pointer):boolean;
begin
  result:=puint64(newvalue)^=puint64(oldvalue)^+value;
end;

function TScanner.QWordIncreasedValueByPercentage(newvalue,oldvalue: pointer):boolean;
begin
  result:=puint64(newvalue)^>trunc(puint64(oldvalue)^+puint64(oldvalue)^*svalue);
end;

function TScanner.QWordDecreasedValue(newvalue,oldvalue: pointer):boolean;
begin
  result:=puint64(newvalue)^<puint64(oldvalue)^;
end;

function TScanner.QWordDecreasedValueBy(newvalue,oldvalue: pointer):boolean;
begin
  result:=puint64(newvalue)^=puint64(oldvalue)^-value;
end;

function TScanner.QWordDecreasedValueByPercentage(newvalue,oldvalue: pointer):boolean;
begin
  result:=puint64(newvalue)^<trunc(puint64(oldvalue)^-puint64(oldvalue)^*svalue);
end;

function TScanner.QWordChanged(newvalue,oldvalue: pointer):boolean;
begin
  result:=puint64(newvalue)^<>puint64(oldvalue)^;
end;

function TScanner.QWordUnchanged(newvalue,oldvalue: pointer):boolean;
begin
  result:=puint64(newvalue)^=puint64(oldvalue)^;
end;


//single:

function TScanner.SingleExact(newvalue,oldvalue: pointer): boolean;
var min,max: single;
begin
  result:=false;
  case roundingtype of
    rtRounded:
      result:=(RoundTo(psingle(newvalue)^,-floataccuracy)=svalue);

    rtExtremerounded:
      result:=(psingle(newvalue)^>minsvalue) and (psingle(newvalue)^<maxsvalue);

    rtTruncated:
      result:=(psingle(newvalue)^>=svalue) and (psingle(newvalue)^<maxsvalue);
  end;

end;

function TScanner.SingleBetween(newvalue,oldvalue: pointer):boolean;
begin
  result:=(psingle(newvalue)^>=svalue) and (psingle(newvalue)^<=svalue2);
end;

function TScanner.SingleBiggerThan(newvalue,oldvalue: pointer):boolean;
begin
  result:=psingle(newvalue)^>svalue;
end;

function TScanner.SingleSmallerThan(newvalue,oldvalue: pointer):boolean;
begin
  result:=psingle(newvalue)^<svalue;
end;

function TScanner.SingleIncreasedValue(newvalue,oldvalue: pointer):boolean;
begin
  result:=psingle(newvalue)^>psingle(oldvalue)^;
end;

function TScanner.SingleIncreasedValueBy(newvalue,oldvalue: pointer):boolean;
begin
  result:=RoundTo(psingle(newvalue)^,-floataccuracy)=RoundTo(psingle(oldvalue)^+svalue,-floataccuracy);
end;

function TScanner.SingleIncreasedValueByPercentage(newvalue,oldvalue: pointer):boolean;
begin
  result:=psingle(newvalue)^>psingle(oldvalue)^+psingle(oldvalue)^*svalue;
end;


function TScanner.SingleDecreasedValue(newvalue,oldvalue: pointer):boolean;
begin
  result:=psingle(newvalue)^<psingle(oldvalue)^;
end;

function TScanner.SingleDecreasedValueBy(newvalue,oldvalue: pointer):boolean;
begin
  result:=RoundTo(psingle(newvalue)^,-floataccuracy)=RoundTo(psingle(oldvalue)^-svalue,-floataccuracy);
end;

function TScanner.SingleDecreasedValueByPercentage(newvalue,oldvalue: pointer):boolean;
begin
  result:=psingle(newvalue)^<psingle(oldvalue)^-psingle(oldvalue)^*svalue;
end;


function TScanner.SingleChanged(newvalue,oldvalue: pointer):boolean;
begin
  result:=pSingle(newvalue)^<>pSingle(oldvalue)^;
end;

function TScanner.SingleUnchanged(newvalue,oldvalue: pointer):boolean;
begin
  result:=pSingle(newvalue)^=pSingle(oldvalue)^;
end;

//double:
function TScanner.DoubleExact(newvalue,oldvalue: pointer): boolean;
begin
  result:=false;
  case roundingtype of
    rtRounded:
      result:=(RoundTo(pdouble(newvalue)^,-floataccuracy)=dvalue);

    rtExtremerounded:
      result:=(pdouble(newvalue)^>mindvalue) and (pdouble(newvalue)^<maxdvalue);

    rtTruncated:
      result:=(pdouble(newvalue)^>=dvalue) and (pdouble(newvalue)^<maxdvalue);
  end;
end;

function TScanner.DoubleBetween(newvalue,oldvalue: pointer):boolean;
begin
  result:=(pdouble(newvalue)^>=dvalue) and (pdouble(newvalue)^<=dvalue2);
end;

function TScanner.DoubleBiggerThan(newvalue,oldvalue: pointer):boolean;
begin
  result:=pdouble(newvalue)^>dvalue;
end;

function TScanner.DoubleSmallerThan(newvalue,oldvalue: pointer):boolean;
begin
  result:=pdouble(newvalue)^<dvalue;
end;

function TScanner.DoubleIncreasedValue(newvalue,oldvalue: pointer):boolean;
begin
  result:=pdouble(newvalue)^>pdouble(oldvalue)^;
end;

function TScanner.DoubleIncreasedValueBy(newvalue,oldvalue: pointer):boolean;
begin
  result:=RoundTo(pdouble(newvalue)^,-floataccuracy)=RoundTo(pdouble(oldvalue)^+svalue,-floataccuracy);
end;

function TScanner.DoubleIncreasedValueByPercentage(newvalue,oldvalue: pointer):boolean;
begin
  result:=pdouble(newvalue)^>pdouble(oldvalue)^+pdouble(oldvalue)^*dvalue;
end;


function TScanner.DoubleDecreasedValue(newvalue,oldvalue: pointer):boolean;
begin
  result:=pdouble(newvalue)^<pdouble(oldvalue)^;
end;

function TScanner.DoubleDecreasedValueBy(newvalue,oldvalue: pointer):boolean;
begin
  result:=RoundTo(pdouble(newvalue)^,-floataccuracy)=RoundTo(pdouble(oldvalue)^-svalue,-floataccuracy);
end;

function TScanner.DoubleDecreasedValueByPercentage(newvalue,oldvalue: pointer):boolean;
begin
  result:=pdouble(newvalue)^<pdouble(oldvalue)^-pdouble(oldvalue)^*dvalue;
end;

function TScanner.DoubleChanged(newvalue,oldvalue: pointer):boolean;
begin
  result:=pDouble(newvalue)^<>pDouble(oldvalue)^;
end;

function TScanner.DoubleUnchanged(newvalue,oldvalue: pointer):boolean;
begin
  result:=pDouble(newvalue)^=pDouble(oldvalue)^;
end;

//=============Save result routines===========//

procedure TScanner.GenericSaveResult(address: dword; oldvalue: pointer);
{
Generic routine for storing results. Use as last resort. E.g custom scans
}
begin
  //save varsize
  pdwordarray(CurrentAddressBuffer)[found]:=address;
  copyMemory(pointer(dword(CurrentFoundBuffer)+(variablesize*found)),oldvalue,variablesize);

  inc(found);
  if found>=buffersize then
    flushroutine;
end;

procedure TScanner.ByteSaveResult(address: dword; oldvalue: pointer);
begin
  //save address and current value
  pdwordarray(CurrentAddressBuffer)[found]:=address;
  pbytearray(CurrentFoundBuffer)[found]:=pbyte(oldvalue)^;

  inc(found);
  if found>=buffersize then
    flushroutine;
end;

procedure TScanner.WordSaveResult(address: dword; oldvalue: pointer);
begin
  pdwordarray(CurrentAddressBuffer)[found]:=address;
  pwordarray(CurrentFoundBuffer)[found]:=pword(oldvalue)^;

  inc(found);
  if found>=buffersize then
    flushroutine;
end;

procedure TScanner.DWordSaveResult(address: dword; oldvalue: pointer);
begin
  pdwordarray(CurrentAddressBuffer)[found]:=address;
  pdwordarray(CurrentFoundBuffer)[found]:=pdword(oldvalue)^;

  inc(found);
  if found>=buffersize then
    flushroutine;
end;

procedure TScanner.QWordSaveResult(address: dword; oldvalue: pointer);
begin
  pdwordarray(CurrentAddressBuffer)[found]:=address;
  puint64array(CurrentFoundBuffer)[found]:=puint64(oldvalue)^;

  inc(found);
  if found>=buffersize then
    flushroutine;
end;

procedure TScanner.SingleSaveResult(address: dword; oldvalue: pointer);
begin
  if not (isnan(psingle(oldvalue)^) or IsInfinite(psingle(oldvalue)^))  then
  begin
    pdwordarray(CurrentAddressBuffer)[found]:=address;
    psinglearray(CurrentFoundBuffer)[found]:=psingle(oldvalue)^;

    inc(found);
    if found>=buffersize then
      flushroutine;
  end;
end;

procedure TScanner.DoubleSaveResult(address: dword; oldvalue: pointer);
begin
  if not (isnan(pdouble(oldvalue)^) or IsInfinite(pdouble(oldvalue)^))  then
  begin
    pdwordarray(CurrentAddressBuffer)[found]:=address;
    pdoublearray(CurrentFoundBuffer)[found]:=pdouble(oldvalue)^;

    inc(found);
    if found>=buffersize then
      flushroutine;
  end;
end;


procedure TScanner.arrayOfByteSaveResult(address: dword; oldvalue: pointer);
begin
  pdwordarray(CurrentAddressBuffer)[found]:=address;
  inc(found);
  if found>=buffersize then
    flushroutine;  
end;

procedure TScanner.binarySaveResult(address: dword; oldvalue: pointer);
var i: integer;
begin
  for i:=0 to 7 do
  begin
    if binaryresults[i] then
    begin
      PBitAddressArray(CurrentAddressBuffer)[found].address:=address;
      PBitAddressArray(CurrentAddressBuffer)[found].bit:=i;
      inc(found);
      if found>=buffersize then
        flushroutine;
    end;
  end;

end;

procedure TScanner.allSaveResult(address: dword; oldvalue: pointer);
{
note: eventually replace bit with a binary representation of all types that match
BUT, don't forget to change the foundlisthelper to handle this (since it'd be
multiple addresses in one entry, which isn't handled right now... 
}
var i: TVariabletype;
begin
  for i:=vtByte to vtDouble do
  begin
    if typesmatch[i] then
    begin
      if (i=vtsingle) then
      begin
        //filter out NAN and INF
        if isnan(psingle(oldvalue)^) or IsInfinite(psingle(oldvalue)^) then
          continue; //skip, don't save
      end;

      if (i=vtdouble) then
      begin
        if isnan(pdouble(oldvalue)^) or IsInfinite(pdouble(oldvalue)^) then
          continue; //skip, don't save
      end;

      //using the bitaddressarray since it holds a address and a value big enough to hold all types
      PBitAddressArray(CurrentAddressBuffer)[found].address:=address;
      PBitAddressArray(CurrentAddressBuffer)[found].bit:=integer(i);

      //save the current address, max variablesize is 8 bytes, so just store that
      puint64array(CurrentFoundBuffer)[found]:=puint64(oldvalue)^;

      inc(found);
      if found>=buffersize then
        flushroutine;
    end;
  end;
end;


//=============Flush result routines===========//
procedure TScanner.genericFlush;
var tempaddress,tempfound: pointer;
begin
  scanwriter.writeresults(CurrentAddressBuffer,currentfoundbuffer,4*found,found*variablesize);
  tempaddress:=CurrentAddressBuffer;
  tempfound:=CurrentFoundBuffer;
  CurrentAddressBuffer:=SecondaryAddressBuffer;
  CurrentFoundBuffer:=SecondaryFoundBuffer;
  SecondaryAddressBuffer:=tempaddress;
  SecondaryFoundBuffer:=tempfound;

  inc(totalfound,found);
  found:=0;
end;

procedure TScanner.stringFlush;
begin
  AddressFile.WriteBuffer(CurrentAddressBuffer^,4*found);
  inc(totalfound,found);
  found:=0;
end;

procedure TScanner.binaryFlush;
begin
  AddressFile.WriteBuffer(CurrentAddressBuffer^,sizeof(TBitAddress)*found);
  inc(totalfound,found);
  found:=0;
end;

procedure TScanner.allFlush;
var tempaddress,tempfound: pointer;
begin
  scanwriter.writeresults(CurrentAddressBuffer,currentfoundbuffer,sizeof(tbitaddress)*found,found*8);
  tempaddress:=CurrentAddressBuffer;
  tempfound:=CurrentFoundBuffer;
  CurrentAddressBuffer:=SecondaryAddressBuffer;
  CurrentFoundBuffer:=SecondaryFoundBuffer;
  SecondaryAddressBuffer:=tempaddress;
  SecondaryFoundBuffer:=tempfound;

  inc(totalfound,found);
  found:=0;
end;

//==================Tscanfilewriter=================//
procedure Tscanfilewriter.execute;
var part: integer;
begin
  part:=0;
  if writeError then exit;
  
  try

    
    repeat
      dataavailable.WaitFor(infinite);
      try

        if terminated then exit;


        scancontroller.resultsaveCS.Enter;
        try

          //note: Compressing before writing using TCompressStream=failure
          //on fast compression speed it still took longer to compess and write
          //than to only write the uncompressed buffer (no compress=2.8 secs,
          //compressed=7.1 sec)

  //        cAddressFile.WriteBuffer(addressbuffer^,addressSize);
  //        cMemoryFile.WriteBuffer(memorybuffer^,memorySize);

          part:=1;
          AddressFile.WriteBuffer(addressbuffer^,addressSize);

          part:=2;
          MemoryFile.WriteBuffer(memorybuffer^,memorySize);
        finally
          scancontroller.resultsaveCS.Leave;
        end;

      finally
        datawritten.SetEvent; //tell the others that you're ready to write again
      end;

    until terminated;
  except
    on e: exception do
    begin
      case part of
        0: errorstring:='Thread synchronizer';
        1: errorstring:=scanner.Addressfilename;
        2: errorstring:=scanner.MemoryFilename;
        else errorstring:='Unknown';
      end;

      errorstring:=errorstring+':'+e.message;
      writeError:=true;
      dataavailable.SetEvent;
      datawritten.SetEvent;
    end;
  end;



end;

procedure Tscanfilewriter.writeresults(addressbuffer,memorybuffer: pointer; addressSize,memorySize: dword);
{
check if the thread is currently saving
If yes, wait, if not, start the thread, give it the buffer, and continue
}
begin
  datawritten.WaitFor(infinite); //only gets set when the thread is done writing
  //got past the wait, so it's done writing, so it has no need for the current variables
  self.addressbuffer:=addressbuffer;
  self.memorybuffer:=memorybuffer;
  self.addressSize:=addressSize;
  self.memorySize:=memorySize;

  dataavailable.SetEvent;  //tell the thread to start saving

  //and return to the scanner, who should now swap scanbuffers
end;

procedure Tscanfilewriter.flush;
begin
  if writeerror then exit;
  datawritten.WaitFor(infinite);
  datawritten.SetEvent;
end;

destructor Tscanfilewriter.destroy;
begin
  if not terminated then
  begin
    terminate;
    dataavailable.SetEvent;
    waitfor;
  end;
  if datawritten<>nil then datawritten.Free;
  if dataavailable<>nil then dataavailable.Free;
{
  if cAddressFile<>nil then cAddressFile.free;
  if cMemoryFile<>nil then cMemoryFile.free;
}
  inherited destroy;
end;

constructor Tscanfilewriter.create(scanner: TScanner; scancontroller:TScanController; addressfile,memoryfile:TFileStream);
begin
  self.scancontroller:=scancontroller;
  self.scanner:=scanner;
  self.addressfile:=addressfile;
  self.memoryfile:=memoryfile;

  //create the events
  datawritten:=tevent.Create(nil,false,true,'');
  dataavailable:=tevent.create(nil,false,false,'');

  inherited create(false); //start it  
end;

//=============TScanner===========//


procedure TScanner.FirstScanmem(base:dword; buffer: pointer; size: integer);
{
Scan the given buffer
Excludes the previousvalue buffer
}
var stepsize: integer;
    lastmem: dword;
    p: pbyte;
    i: TVariableType;
    _fastscan: boolean;
    dividableby2: boolean;
    dividableby4: boolean;
begin
  _fastscan:=fastscan;
  if _fastscan then
    stepsize:=fastscanalignsize
  else
    stepsize:=1;

  p:=buffer;

  lastmem:=dword(p)+(size-variablesize); //optimizes compile to use reg if possible


  if variableType=vtAll then
  begin
    //reset typesmatch array for each check
    while (dword(p)<=lastmem) do
    begin
      if _fastscan then
      begin
        dividableby2:=dword(p) mod 2=0;
        dividableby4:=dword(p) mod 4=0;
        typesmatch[vtByte]:=true;
        typesmatch[vtWord]:=dividableby2;
        typesmatch[vtDWord]:=dividableby4;
        typesmatch[vtQWord]:=dividableby4;
        typesmatch[vtSingle]:=dividableby4;
        typesmatch[vtDouble]:=dividableby4;
      end
      else
        for i:=vtByte to vtDouble do typesmatch[i]:=true;

      if checkroutine(p,nil) then //found one
        StoreResultRoutine(base+dword(p)-dword(buffer),p);

      inc(p,stepsize);
    end;    
  end
  else
  begin
    while (dword(p)<=lastmem) do
    begin
      if checkroutine(p,nil) then //found one
      begin
        StoreResultRoutine(base+dword(p)-dword(buffer),p);
        if OnlyOne then
        begin
          AddressFound:=base+dword(p)-dword(buffer);
          exit;
        end;
      end;

      inc(p,stepsize);
    end;
  end;
end;

procedure TScanner.FirstNextScanmem(base:dword; buffer,oldbuffer: pointer; size: integer);
{
Scan the given buffer
}
var stepsize:  integer;
    lastmem:   dword;
    p,oldp:    pbyte;
    valuetype: TValueType;
    _fastscan: boolean;
    dividableby2: boolean;
    dividableby4: boolean;
    i:         TVariableType;   
begin
  _fastscan:=fastscan;
  if _fastscan then
    stepsize:=fastscanalignsize
  else
    stepsize:=1;

  p:=buffer;
  oldp:=oldbuffer;

  lastmem:=dword(p)+(size-variablesize); //optimizes compile to use reg if possible

{$ifndef standalonetrainerwithassemblerandaobscanner}
  if scanOption=soSameAsFirst then //stupid, but ok...
  begin
    case self.variableType of
      vtByte:   valuetype:=vt_byte;
      vtWord:   valuetype:=vt_word;
      vtDWord:  valuetype:=vt_dword;
      vtSingle: valuetype:=vt_single;
      vtdouble: valuetype:=vt_double;
      vtQword:  valuetype:=vt_int64;
      vtAll:    valuetype:=vt_all;
    end;

    if valuetype=vt_all then
    begin
      while dword(p)<=lastmem do
      begin
        if _fastscan then
        begin
          dividableby2:=dword(p) mod 2=0;
          dividableby4:=dword(p) mod 4=0;
          typesmatch[vtByte]:=true;
          typesmatch[vtWord]:=dividableby2;
          typesmatch[vtDWord]:=dividableby4;
          typesmatch[vtQWord]:=dividableby4;
          typesmatch[vtSingle]:=dividableby4;
          typesmatch[vtDouble]:=dividableby4;
        end
        else
          for i:=vtByte to vtDouble do typesmatch[i]:=true;

        if checkroutine(p,firstscanhandler.getpointertoaddress(base+dword(p)-dword(buffer),valuetype )) then //found one
          StoreResultRoutine(base+dword(p)-dword(buffer),p);

        inc(p, stepsize);
      end;
    end
    else
    begin
      while dword(p)<=lastmem do
      begin
        if checkroutine(p,firstscanhandler.getpointertoaddress(base+dword(p)-dword(buffer),valuetype )) then //found one
          StoreResultRoutine(base+dword(p)-dword(buffer),p);

        inc(p, stepsize);
      end;
    end;
  end
  else
  {$endif}
  begin
    if variableType=vtall then
    begin
      while dword(p)<=lastmem do
      begin
        if _fastscan then
        begin
          dividableby2:=dword(p) mod 2=0;
          dividableby4:=dword(p) mod 4=0;
          typesmatch[vtByte]:=true;
          typesmatch[vtWord]:=dividableby2;
          typesmatch[vtDWord]:=dividableby4;
          typesmatch[vtQWord]:=dividableby4;
          typesmatch[vtSingle]:=dividableby4;
          typesmatch[vtDouble]:=dividableby4;
        end
        else
          for i:=vtByte to vtDouble do typesmatch[i]:=true;

        if checkroutine(p,oldp) then //found one
          StoreResultRoutine(base+dword(p)-dword(buffer),p);

        inc(p, stepsize);
        inc(oldp, stepsize);
      end;
    end
    else
    begin
      while dword(p)<=lastmem do
      begin
        if checkroutine(p,oldp) then //found one
          StoreResultRoutine(base+dword(p)-dword(buffer),p);

        inc(p, stepsize);
        inc(oldp, stepsize);
      end;
    end;
  end;
end;

procedure TScanner.nextnextscanmemAll(addresslist: pointer; oldmemory: pointer; chunksize: integer);
var stepsize: dword;
    i,j,k: dword;
    l: TVariableType;
    maxindex: dword;
    vsize: dword;
    currentbase: dword;
    newmemory: array [0..4095] of byte;
    oldmem: pbytearray;
    alist: pbitaddressarray;
    actualread: dword;

    so: Tscanoption;
    valuetype: TValuetype;
    currentaddress: dword;
begin
  i:=0;
  so:=scanoption;
  maxindex:=chunksize;
  vsize:=variablesize; //=8
  oldmem:=oldmemory;
  alist:=addresslist;
{$ifndef standalonetrainerwithassemblerandaobscanner}
  valuetype:=vt_all;
{$endif}

  while i<maxindex do
  begin
    j:=i+1;
    currentbase:=alist[i].address and $FFFFF000;
    while j<=maxindex do
    begin


        
      if (currentbase)=((alist[j].address+vsize-1) and $fffff000) then //same page
        inc(j)
      else
      begin
        dec(j);  //now points to the last valid one, or the first one
        break;
      end;
    end;

    currentbase:=alist[i].address;
    if readprocessmemory(processhandle,pointer(currentbase),@newmemory[0],(alist[j].address-currentbase)+vsize,actualread) then
    begin
{$ifndef standalonetrainerwithassemblerandaobscanner}
      if so=soSameAsFirst then
      begin
        //clear typesmatch and set current address
        for l:=vtByte to vtDouble do
          typesmatch[l]:=false;
          
        currentaddress:=currentbase;

        for k:=i to j do
        begin
          if alist[k].address=currentaddress then
          begin
            typesmatch[tvariabletype(alist[k].bit)]:=true;
          end
          else
          begin
            if checkroutine(@newmemory[currentaddress-currentbase],firstscanhandler.getpointertoaddress(currentaddress,valuetype )) then
              StoreResultRoutine(currentaddress,@newmemory[currentaddress-currentbase]);

            //clear typesmatch and set current address
            for l:=vtByte to vtDouble do
              typesmatch[l]:=false;

            currentaddress:=alist[k].address;
            typesmatch[tvariabletype(alist[k].bit)]:=true;
          end;

        end;

        if checkroutine(@newmemory[currentaddress-currentbase],firstscanhandler.getpointertoaddress(currentaddress,valuetype )) then
          StoreResultRoutine(currentaddress,@newmemory[currentaddress-currentbase]);

      end
      else
{$endif}      
      begin
        //clear typesmatch and set current address
        for l:=vtByte to vtDouble do
          typesmatch[l]:=false;
          
        currentaddress:=currentbase;

        for k:=i to j do
        begin
          if alist[k].address=currentaddress then
          begin
            //add this one to the list
            typesmatch[tvariabletype(alist[k].bit)]:=true;
            continue;
          end
          else
          begin
            //we now have a list of entries with all the same address, k-1 points to the last one
            if CheckRoutine(@newmemory[currentaddress-currentbase],@oldmem[(k-1)*vsize]) then
              StoreResultRoutine(currentaddress,@newmemory[currentaddress-currentbase]);

            //clear typesmatch and set current address
            for l:=vtByte to vtDouble do
              typesmatch[l]:=false;

            currentaddress:=alist[k].address;

            typesmatch[tvariabletype(alist[k].bit)]:=true;
          end;

        end;

        if CheckRoutine(@newmemory[currentaddress-currentbase],@oldmem[j*vsize]) then
          StoreResultRoutine(currentaddress,@newmemory[currentaddress-currentbase]);
      end;
    end;

    i:=j+1;
  end;
end;


procedure TScanner.nextnextscanmembinary(addresslist: pointer; chunksize: integer);
var stepsize: dword;
    i,j,k,l: dword;
    maxindex: dword;
    vsize: dword;
    currentbase: dword;
    newmemory: array [0..4095] of byte;
    oldmem: pbytearray;
    alist: PBitAddressArray;
    actualread: dword;
    lastaddress: dword;
    scannedbitlist: array [0..7] of boolean;
begin
  i:=0;
  maxindex:=chunksize;
  vsize:=variablesize;
  alist:=addresslist;
  currentbase:=0;

  while i<maxindex do
  begin
    j:=i+1;
              
    currentbase:=alist[i].address and $FFFFF000;
    while j<=maxindex do
    begin
      if (currentbase)=((alist[j].address+vsize-1) and $fffff000) then //same page
        inc(j)
      else
      begin
        dec(j);  //now points to the last valid one, or the first one
        break;
      end;
    end;



    currentbase:=alist[i].address;
    if readprocessmemory(processhandle,pointer(currentbase),@newmemory[0],(alist[j].address-currentbase)+vsize,actualread) then
    begin
      k:=i;
      while k<=j do
      begin
        if CheckRoutine(@newmemory[alist[k].address-currentbase],nil) then
        begin
          for l:=0 to 7 do
            scannedbitlist[l]:=false;

          lastaddress:=alist[k].address;

          while (alist[k].address=lastaddress) and (k<=j) do //nugfix might cause performance loss, check it later
          begin
            //add bits to the scanned bitlist
            scannedbitlist[alist[k].bit]:=true;
            inc(k);
          end;
          dec(k);

          for l:=0 to 7 do
            if not scannedbitlist[l] then binaryresults[l]:=false; //if it wasn't scanned, but the result was true, then set it to false

          StoreResultRoutine(lastaddress,@newmemory[lastaddress-currentbase]);
        end;
        inc(k); //next
      end;
    end;

    i:=j+1;
  end;
end;

procedure TScanner.nextnextscanmem(addresslist: pointer; oldmemory: pointer; chunksize: integer);
{
gather addresses in a 4K region and readprocessmemory the difference between (so if only 2 addresses in a 4K block and address 1 is at 0 and address 2 at 2k, then only read 2k)
note: Do a test to see how many cpu cycles it costs to read 4K and 4bytes and find out the minimum number for mos toptimal speed
done: 4 byte scan takes an average of 4000 cycles (due to caches etc...)
   4096 byte scan takes an average of 6500 cycles ...
   2048 byte scan takes an average of 5900 cycles ...

conclusion: only when there is 1 address in the list, scan 1 address, else scan more

}

var stepsize: dword;
    i,j,k: dword;
    maxindex: dword;
    vsize: dword;
    currentbase: dword;
    newmemory: array [0..4095] of byte;
    oldmem: pbytearray;
    alist: pdwordarray;
    actualread: dword;

    so: Tscanoption;
{$ifndef standalonetrainerwithassemblerandaobscanner}
    valuetype: TValuetype;
{$endif}
    currentaddress: dword;
begin
  i:=0;
  so:=scanoption;
  maxindex:=chunksize;
  vsize:=variablesize;
  oldmem:=oldmemory;
  alist:=addresslist;

{$ifndef standalonetrainerwithassemblerandaobscanner}
  case variableType of
    vtByte:   valuetype:=vt_byte;
    vtWord:   valuetype:=vt_word;
    vtDWord:  valuetype:=vt_dword;
    vtSingle: valuetype:=vt_single;
    vtdouble: valuetype:=vt_double;
    vtQword:  valuetype:=vt_int64;
    vtAll:    valuetype:=vt_all;
    vtCustom:
    begin
      case vsize of
        1: valuetype:=vt_byte;
        2: valuetype:=vt_word;
        4: valuetype:=vt_dword;
        8: valuetype:=vt_int64;
        else valuetype:=vt_dword;
      end;

    end;
  end;
{$endif}

  while i<maxindex do
  begin
    j:=i+1;
    currentbase:=alist[i] and $FFFFF000;
    while j<=maxindex do
    begin
      if (currentbase)=((alist[j]+vsize-1) and $fffff000) then //same page
        inc(j)
      else
      begin
        dec(j);  //now points to the last valid one, or the first one
        break;
      end;
    end;


    currentbase:=alist[i];
    if readprocessmemory(processhandle,pointer(currentbase),@newmemory[0],(alist[j]-currentbase)+vsize,actualread) then
    begin
{$ifndef standalonetrainerwithassemblerandaobscanner}
      if so=soSameAsFirst then
      begin
        for k:=i to j do
          if checkroutine(@newmemory[alist[k]-currentbase],firstscanhandler.getpointertoaddress(alist[k],valuetype )) then
            StoreResultRoutine(alist[k],@newmemory[alist[k]-currentbase])
      end
      else
{$endif}
      begin
        for k:=i to j do
          if CheckRoutine(@newmemory[alist[k]-currentbase],@oldmem[k*vsize]) then
            StoreResultRoutine(alist[k],@newmemory[alist[k]-currentbase]);
      end;
    end;

    i:=j+1;
  end;
end;

procedure TScanner.configurescanroutine;
{
Parse scanvalue1 and scanvalue2 accordingly to the scanoptions and type
and fill in class variables that will be used for the scans
}
var FloatSettings: TFormatSettings;
    BinaryString,andmask,bitmask: string;
    i: integer;
    b: Tbytes;
    foundbuffersize: integer;
    p: pointer;
begin
  //fill FloatSettings with formatting data (e.g difference between , and . for decimal)
  GetLocaleFormatSettings(GetThreadLocale, FloatSettings);


  if scanOption in [soCustom, soExactValue,soValueBetween,soBiggerThan,soSmallerThan, soDecreasedValueBy, soIncreasedValueBy] then
  begin
    //user input is given
    if scanvalue1='' then raise exception.Create('Please fill something in');

    if variableType in [vtByte,vtWord,vtDWord,vtQword,vtAll,vtCustom] then
    begin
      //parse scanvalue1
      try
        if hexadecimal then
          value:=strtoint64('$'+scanvalue1)
        else
          value:=strtoint64(scanvalue1);
      except
        if variableType=vtAll then
        begin
          try
            dvalue:=strtofloat(scanvalue1,FloatSettings);
          except
            if FloatSettings.DecimalSeparator=',' then
              FloatSettings.DecimalSeparator:='.'
            else
              FloatSettings.DecimalSeparator:=',';

            //try again
            try
              dvalue:=strtofloat(scanvalue1,FloatSettings);
            except
              if variabletype<>vtCustom then
                raise exception.Create(scanvalue1+' is not a valid value');
            end;
          end;
          value:=trunc(dvalue);

        end else
          if variabletype<>vtCustom then
            raise exception.Create(scanvalue1+' is an invalid value');
      end;

      if scanOption=soValueBetween then
      begin
        //also parse scanvalue2
        try
          if hexadecimal then
            value2:=strtoint64('$'+scanvalue2)
          else
            value2:=strtoint64(scanvalue2);

        except
          if variableType=vtAll then
          begin
            try
              dvalue:=strtofloat(scanvalue2,FloatSettings);
            except
              if FloatSettings.DecimalSeparator=',' then
                FloatSettings.DecimalSeparator:='.'
              else
                FloatSettings.DecimalSeparator:=',';

              //try again
              try
                dvalue:=strtofloat(scanvalue1,FloatSettings);
              except
                if variabletype<>vtCustom then
                  raise exception.Create(scanvalue1+' is not a valid value');
              end;
            end;
            value:=trunc(dvalue);
          end
          else
            if variabletype<>vtCustom then
              raise exception.Create(scanvalue2+' is an invalid value');
        end;
      end;
    end;

    if percentage or (variableType in [vtsingle,vtDouble,vtAll,vtCustom]) then
    begin
      try
        dvalue:=strtofloat(scanvalue1,FloatSettings);
      except
        if FloatSettings.DecimalSeparator=',' then
          FloatSettings.DecimalSeparator:='.'
        else
          FloatSettings.DecimalSeparator:=',';

        //try again
        try
          dvalue:=strtofloat(scanvalue1,FloatSettings);
        except
          if variabletype<>vtCustom then
            raise exception.Create(scanvalue1+' is not a valid value');
        end;
      end;

      if scanoption=soValueBetween then
      begin
        try
          dvalue2:=strtofloat(scanvalue2,FloatSettings);
        except
          if FloatSettings.DecimalSeparator=',' then
            FloatSettings.DecimalSeparator:='.'
          else
            FloatSettings.DecimalSeparator:=',';

          //try again
          try
            dvalue2:=strtofloat(scanvalue1,FloatSettings);
          except
            if variabletype<>vtCustom then
              raise exception.Create(scanvalue1+' is not a valid value');
          end;
        end;

      end;

      if percentage then
        dvalue:=dvalue / 100;

      svalue:=dvalue;
      svalue2:=dvalue2;

      if not percentage then //if it's not a percentage scan then round the userinput
      begin
        floataccuracy:=pos(FloatSettings.DecimalSeparator,scanvalue1);
        if floataccuracy>0 then
          floataccuracy:=length(scanvalue1)-floataccuracy;

        svalue:=RoundTo(svalue,-floataccuracy);
        svalue2:=RoundTo(svalue2,-floataccuracy);
        dvalue:=RoundTo(dvalue,-floataccuracy);
        dvalue2:=RoundTo(dvalue2,-floataccuracy);
      end;


      mindvalue:=dvalue-(1/(power(10,floataccuracy)));
      maxdvalue:=dvalue+(1/(power(10,floataccuracy)));
      minsvalue:=svalue-(1/(power(10,floataccuracy)));
      maxsvalue:=svalue+(1/(power(10,floataccuracy)));



    end;



                  
    if variableType = vtString then
    begin
      widescanvalue1:=scanvalue1;
    end;    

    if variabletype = vtByteArray then
    begin
      ConvertStringToBytes(scanvalue1,hexadecimal,abs_arraytofind);
      abs_arraylength:=length(abs_arraytofind);
    end;

    if variableType = vtBinary then
    begin
      if binaryStringAsDecimal then
      begin
        if hexadecimal then
        begin
          scanvalue1:=scanvalue1+'$';
          scanvalue2:=scanvalue2+'$';
        end;

        binarystring:=inttobin(strtoint(scanvalue1))
      end
      else
        binarystring:=scanvalue1;

      andmask:='';
      bitmask:='';

      for i:=1 to length(binarystring) do
      begin
        if binarystring[i] in ['?','*'] then
        begin
          andmask:=andmask+'0';
          bitmask:=bitmask+'0';
        end
        else
        if binarystring[i] in ['0','1'] then
        begin
          andmask:=andmask+'1';
          bitmask:=bitmask+binarystring[i];
        end
        else
        if not (binarystring[i] in [' ',#8]) then
          raise exception.Create(binarystring+' is not a valid notation');
      end;

      self.andmask:=BinToInt(andmask);
      self.bitmask:=bintoint(bitmask);


    end;


  end;

  FlushRoutine:=genericFlush; //change if not so

  if variableType in [vtbinary,vtall] then
  begin
    getmem(CurrentAddressBuffer,buffersize*sizeof(Tbitaddress));
    getmem(SecondaryAddressBuffer,buffersize*sizeof(Tbitaddress));
  end
  else
  begin
    getmem(CurrentAddressBuffer,buffersize*4);
    getmem(SecondaryAddressBuffer,buffersize*4);
  end;

{$ifndef standalonetrainerwithassemblerandaobscanner}
  if scanOption=soSameAsFirst then //create a first scan handler
    FirstScanHandler:=TFirstscanhandler.create;
{$endif}

  case variableType of
    vtByte:
    begin
      //byte config
      FoundBufferSize:=buffersize*1;
      StoreResultRoutine:=ByteSaveResult;

      case scanOption of
        soExactValue:       checkRoutine:=byteExact;
        soValueBetween:     checkroutine:=byteBetween;
        soBiggerThan:       checkroutine:=byteBiggerThan;
        soSmallerThan:      checkroutine:=byteSmallerThan;
        soIncreasedValue:   checkroutine:=byteIncreasedValue;
        soIncreasedValueBy: if percentage then
                              checkroutine:=byteIncreasedValueByPercentage        
                            else
                              checkroutine:=byteIncreasedValueBy;
        soDecreasedValue:   checkroutine:=byteDecreasedValue;
        soDecreasedValueBy: if percentage then
                              checkroutine:=byteDecreasedValueByPercentage
                            else
                              checkroutine:=byteDecreasedValueBy;
        soChanged:          checkroutine:=byteChanged;
        soUnChanged:        checkroutine:=byteUnchanged;
        soSameAsFirst:      checkroutine:=byteUnchanged;
      end;
    end;

    vtWord:
    begin
      //word config
      FoundBufferSize:=buffersize*2;
      StoreResultRoutine:=WordSaveResult;

      case scanOption of
        soExactValue:       checkRoutine:=wordExact;
        soValueBetween:     checkroutine:=wordBetween;
        soBiggerThan:       checkroutine:=wordBiggerThan;
        soSmallerThan:      checkroutine:=wordSmallerThan;
        soIncreasedValue:   checkroutine:=wordIncreasedValue;
        soIncreasedValueBy: if percentage then
                              checkroutine:=wordIncreasedValueByPercentage
                            else
                              checkroutine:=wordIncreasedValueBy;
        soDecreasedValue:   checkroutine:=wordDecreasedValue;
        soDecreasedValueBy: if percentage then
                              checkroutine:=wordDecreasedValueByPercentage        
                            else
                              checkroutine:=wordDecreasedValueBy;
        soChanged:          checkroutine:=wordChanged;
        soUnChanged:        checkroutine:=wordUnchanged;
        soSameAsFirst:      checkroutine:=wordUnchanged;
      end;
    end;

    vtDWord:
    begin
      //dword config
      FoundBufferSize:=buffersize*4;
      StoreResultRoutine:=DWordSaveResult;

      case scanOption of
        soExactValue:       checkRoutine:=dwordExact;
        soValueBetween:     checkroutine:=dwordBetween;
        soBiggerThan:       checkroutine:=dwordBiggerThan;
        soSmallerThan:      checkroutine:=dwordSmallerThan;
        soIncreasedValue:   checkroutine:=dwordIncreasedValue;
        soIncreasedValueBy: if percentage then
                              checkroutine:=dwordIncreasedValueByPercentage
                            else
                              checkroutine:=dwordIncreasedValueBy;
        soDecreasedValue:   checkroutine:=dwordDecreasedValue;
        soDecreasedValueBy: if percentage then
                              checkroutine:=DWordDecreasedValueByPercentage
                            else
                              checkroutine:=dwordDecreasedValueBy;
                              
        soChanged:          checkroutine:=dwordChanged;
        soUnChanged:        checkroutine:=dwordUnchanged;
        soSameAsFirst:      checkroutine:=dwordUnchanged;
      end;
    end;

    vtQWord:
    begin
      //qword config
      FoundBufferSize:=buffersize*8;
      StoreResultRoutine:=QWordSaveResult;

      case scanOption of
        soExactValue:       checkRoutine:=qwordExact;
        soValueBetween:     checkroutine:=qwordBetween;
        soBiggerThan:       checkroutine:=qwordBiggerThan;
        soSmallerThan:      checkroutine:=qwordSmallerThan;
        soIncreasedValue:   checkroutine:=qwordIncreasedValue;
        soIncreasedValueBy: if percentage then
                              checkroutine:=qwordIncreasedValueByPercentage
                            else
                              checkroutine:=qwordIncreasedValueBy;

        soDecreasedValue:   checkroutine:=qwordDecreasedValue;
        soDecreasedValueBy: if percentage then
                              checkroutine:=qwordDecreasedValueByPercentage
                            else  
                              checkroutine:=qwordDecreasedValueBy;
        soChanged:          checkroutine:=qwordChanged;
        soUnChanged:        checkroutine:=qwordUnchanged;
        soSameAsFirst:      checkroutine:=qwordUnchanged;
      end;
    end;

    vtSingle:
    begin
      //single config
      FoundBufferSize:=buffersize*4;
      StoreResultRoutine:=SingleSaveResult;

      case scanOption of
        soExactValue:       checkRoutine:=singleExact;
        soValueBetween:     checkroutine:=singleBetween;
        soBiggerThan:       checkroutine:=singleBiggerThan;
        soSmallerThan:      checkroutine:=singleSmallerThan;
        soIncreasedValue:   checkroutine:=singleIncreasedValue;
        soIncreasedValueBy: if percentage then
                              checkroutine:=singleIncreasedValueByPercentage
                            else
                              checkroutine:=singleIncreasedValueBy;
        soDecreasedValue:   checkroutine:=singleDecreasedValue;
        soDecreasedValueBy: if percentage then
                              checkroutine:=singleDecreasedValueByPercentage
                            else
                              checkroutine:=singleDecreasedValueBy;
        soChanged:          checkroutine:=singleChanged;
        soUnChanged:        checkroutine:=singleUnchanged;
        soSameAsFirst:      checkroutine:=singleUnchanged;
      end;
    end;

    vtDouble:
    begin
      //double config
      FoundBufferSize:=buffersize*8;
      StoreResultRoutine:=doubleSaveResult;

      case scanOption of
        soExactValue:       checkRoutine:=doubleExact;
        soValueBetween:     checkroutine:=doubleBetween;
        soBiggerThan:       checkroutine:=doubleBiggerThan;
        soSmallerThan:      checkroutine:=doubleSmallerThan;
        soIncreasedValue:   checkroutine:=doubleIncreasedValue;
        soIncreasedValueBy: if percentage then
                              checkroutine:=doubleIncreasedValueByPercentage
                            else
                              checkroutine:=doubleIncreasedValueBy;
        soDecreasedValue:   checkroutine:=doubleDecreasedValue;
        soDecreasedValueBy: if percentage then
                              checkroutine:=doubleDecreasedValueByPercentage
                            else
                              checkroutine:=doubleDecreasedValueBy;
        soChanged:          checkroutine:=doubleChanged;
        soUnChanged:        checkroutine:=doubleUnchanged;
        soSameAsFirst:      checkroutine:=doubleUnchanged;
      end;
    end;
      
    vtByteArray:
    begin
      CheckRoutine:=ArrayOfByteExact;
      flushroutine:=stringFlush;
      StoreResultRoutine:=ArrayOfByteSaveResult;
      FoundBufferSize:=0;
    end;



    vtBinary:
    begin
      CheckRoutine:=BinaryExact;
      StoreResultRoutine:=binarySaveResult;
      flushroutine:=binaryFlush;
    end;

    vtString:
    begin
      //check if unicode or casesensitive is used
      if not casesensitive then
      begin
        scanvalue1:=uppercase(scanvalue1);
        widescanvalue1:=uppercase(widescanvalue1);
      end;

      flushroutine:=stringFlush;
      
      case scanOption of
        soExactValue:
        begin
          if casesensitive and unicode then CheckRoutine:=CaseSensitiveUnicodeStringExact;
          if casesensitive and not unicode then CheckRoutine:=CaseSensitiveAnsiStringExact;
          if not casesensitive and unicode then CheckRoutine:=CaseInsensitiveUnicodeStringExact;
          if not casesensitive and not unicode then CheckRoutine:=CaseInsensitiveAnsiStringExact;
        end;

      end;

      StoreResultRoutine:=arrayOfByteSaveResult; //arrayOfByteSaveResult is compatible since it saves only the address
    end;

    vtAll:
    begin
      //almost like binary
      variablesize:=8; //override these variables
      fastscanalignsize:=1;

      FoundBufferSize:=buffersize*8;
      StoreResultRoutine:=allSaveResult;
      FlushRoutine:=allFlush;
      case scanOption of
        soExactValue:       checkRoutine:=allExact;
        soValueBetween:     checkroutine:=allBetween;
        soBiggerThan:       checkroutine:=allBiggerThan;
        soSmallerThan:      checkroutine:=allSmallerThan;
        soIncreasedValue:   checkroutine:=allIncreasedValue;
        soIncreasedValueBy: if percentage then
                              checkroutine:=allIncreasedValueByPercentage
                            else
                              checkroutine:=allIncreasedValueBy;
        soDecreasedValue:   checkroutine:=allDecreasedValue;
        soDecreasedValueBy: if percentage then
                              checkroutine:=allDecreasedValueByPercentage
                            else
                              checkroutine:=allDecreasedValueBy;
        soChanged:          checkroutine:=allChanged;
        soUnChanged:        checkroutine:=allUnchanged;
        soSameAsFirst:      checkroutine:=allUnchanged;
      end;
      //the other types have to be filled in by the nextscan routines
    end;

{$ifndef standalonetrainerwithassemblerandaobscanner}    
    vtCustom:
    begin
      case customscantype of
        cstAutoAssembler:
        begin
          //execute autoassemblescript and read out the alloced addresses and vars

          if not autoassemble(customscanscript,false,true,false,true,CustomScanAllocArray) then
            raise exception.Create('Invalid Auto Assembler script');


          for i:=0 to length(CustomScanAllocArray)-1 do
          begin
            if lowercase(CustomScanAllocArray[i].varname)='checkroutine' then
              pointer(@checkroutine):=pointer(CustomScanAllocArray[i].address)
            else
            if lowercase(CustomScanAllocArray[i].varname)='prologue' then
              customprologue:=pointer(CustomScanAllocArray[i].address)
            else
            if lowercase(CustomScanAllocArray[i].varname)='epilogue' then
              customepilogue:=pointer(CustomScanAllocArray[i].address)
            else
            if lowercase(CustomScanAllocArray[i].varname)='fastscanstepsize' then
              fastscanalignsize:=pdword(CustomScanAllocArray[i].address)^
            else
            if lowercase(CustomScanAllocArray[i].varname)='variablesize' then
            begin
              variablesize:=pdword(CustomScanAllocArray[i].address)^;
              OwningScanController.OwningMemScan.customvariablesize:=variablesize;
            end
            else
            if lowercase(CustomScanAllocArray[i].varname)='scantext' then
              pdword(CustomScanAllocArray[i].address)^:=dword(@scanvalue1[1])
            else
            if lowercase(CustomScanAllocArray[i].varname)='scanvalue' then
              puint64(CustomScanAllocArray[i].address)^:=value
            else
            if lowercase(CustomScanAllocArray[i].varname)='singlescanvalue' then
              psingle(CustomScanAllocArray[i].address)^:=svalue
            else
            if lowercase(CustomScanAllocArray[i].varname)='doublescanvalue' then
              pdouble(CustomScanAllocArray[i].address)^:=dvalue
            else
            if lowercase(CustomScanAllocArray[i].varname)='firstscan' then
              if pinteger(CustomScanAllocArray[i].address)^=1 then
                scanoption:=soSameAsFirst;
          end;
          //set the buffersizes to what they will be

          scanOption:=soCustom;

          FoundBufferSize:=buffersize*variablesize;

          //use the generic save routine
          StoreResultRoutine:=GenericSaveResult;

          //and flush
          FlushRoutine:=genericFlush;


          //what better time to call customprologue then the present
          customprologue();
        end;

      end;


    end;
{$endif}    


  end;

  getmem(CurrentFoundBuffer,FoundBufferSize);
  getmem(SecondaryFoundBuffer,FoundBufferSize);
end;

procedure TScanner.nextNextscan;
var oldAddressfile: TFileStream;
    oldMemoryfile: TFileStream;
    i,j: integer;
    stopindex: integer;
    chunksize: integer;

    oldaddresses: array of dword;
    oldaddressesb: array of tbitaddress;
    oldmemory: pointer;

    part: integer;
begin
  configurescanroutine;
  oldAddressFile:=nil;
  oldMemoryFile:=nil;
  oldmemory:=nil;
  try
    oldAddressFile:=TFileStream.Create(CheatEngineDir+'Addresses.TMP',fmOpenRead or fmShareDenyNone);
    oldMemoryFile:=TFileStream.Create(CheatEngineDir+'Memory.TMP',fmOpenRead or fmShareDenyNone);

    //set the current index to startentry
    stopindex:=stopentry-startentry;  //going from 0 to stopindex

    if self.variableType in [vtBinary,vtall] then
    begin
      //addressfile of tbitaddresstype
      setlength(oldaddressesb,buffersize);
      oldAddressFile.seek(7+sizeof(TBitAddress)*startentry,soFromBeginning);
      if self.variableType=vtall then
      begin
        oldmemory:=virtualAlloc(nil,buffersize*variablesize,MEM_COMMIT	or MEM_TOP_DOWN	, PAGE_READWRITE);
        if oldmemory=nil then raise exception.Create('Error allocating '+inttostr(chunksize*variablesize)+' bytes for the old results. chunksize='+inttostr(chunksize)+' variablesize='+inttostr(variablesize));
      end;

      oldMemoryFile.seek(variablesize*startentry,soFromBeginning);
    end
    else
    begin
      setlength(oldaddresses,buffersize);
      oldmemory:=virtualAlloc(nil,buffersize*variablesize,MEM_COMMIT	or MEM_TOP_DOWN	, PAGE_READWRITE);
      if oldmemory=nil then raise exception.Create('Error allocating '+inttostr(chunksize*variablesize)+' bytes for the old results. chunksize='+inttostr(chunksize)+' variablesize='+inttostr(variablesize));

      oldAddressFile.seek(7+sizeof(DWORD)*startentry,soFromBeginning);
      if not (self.variableType in [vtString,vtByteArray]) then
        oldMemoryFile.seek(variablesize*startentry,soFromBeginning);
    end;


    //read in chunks of buffersize
    i:=0;
    while i<=stopindex do
    begin
      chunksize:=stopindex-i+1;
      if chunksize>buffersize then
        chunksize:=buffersize;

      case variabletype of
        vtBinary:
        begin
          oldAddressFile.ReadBuffer(oldaddressesb[0],chunksize*sizeof(tbitaddress));
          nextnextscanmembinary(@oldaddressesb[0],chunksize);
        end;

        vtAll:
        begin
          oldAddressFile.ReadBuffer(oldaddressesb[0],chunksize*sizeof(tbitaddress));
          if scanoption<>soSameAsFirst then
            oldMemoryFile.ReadBuffer(oldmemory^,chunksize*variablesize);
            
          nextnextscanmemall(@oldaddressesb[0],oldmemory,chunksize);
        end;

        else
        begin

          oldAddressFile.ReadBuffer(oldaddresses[0],chunksize*4);

          if scanoption<>soSameAsFirst then
          begin
            if not (self.variableType in [vtString,vtByteArray]) then //skip the types with no previous result stored
              oldMemoryFile.ReadBuffer(oldmemory^,chunksize*variablesize);
          end;


          nextnextscanmem(@oldaddresses[0],oldmemory,chunksize);
        end;
      end;

      inc(scanned,chunksize);
      inc(i,chunksize);
    end;


    flushroutine; //save all results temporarily stored in memory

  finally
    if oldAddressFile<>nil then oldAddressFile.free;
    if oldMemoryFile<>nil then oldMemoryFile.free;
    if oldmemory<>nil then virtualfree(oldmemory,0,MEM_RELEASE);
  end;
end;

procedure TScanner.firstNextscan;
var
  i: integer;
  size: integer;
  currentbase: dword;
  startregion: integer;
  stopregion: integer;
  memorybuffer: ^byte;
  oldbuffer: ^byte;
  toread: integer;
  actualread: dword;
  hasleft: boolean;
begin
  hasleft:=false;
  startregion:=_startregion; //using a variable so stack can be used, with possibility of register
  stopregion:=_stopregion;

  //allocate a buffer for reading the new memory buffer
  memorybuffer:=virtualAlloc(nil,maxregionsize+(variablesize-1),MEM_COMMIT	or MEM_TOP_DOWN	, PAGE_READWRITE);
  try
    //configure some variables and function pointers
    configurescanroutine;

    for i:=startregion to stopregion do
    begin
      if terminated or OwningScanController.Terminated then exit;
      if i=startregion then
      begin
        currentbase:=startaddress;
        toread:=OwningScanController.memregion[i].MemorySize-(startaddress-OwningScanController.memregion[i].BaseAddress);

        //set oldbuffer and point to the exact start
        oldbuffer:=OwningScanController.memregion[i].startaddress;
        inc(oldbuffer, startaddress-OwningScanController.memregion[i].BaseAddress);
      end
      else
      begin
        currentbase:=OwningScanController.memregion[i].BaseAddress;
        toread:=OwningScanController.memregion[i].MemorySize;

        oldbuffer:=OwningScanController.memregion[i].startaddress;
      end;

      if (i=stopregion) and ((currentbase+toread)>stopaddress) then
        toread:=stopaddress-currentbase;

      //also try to read the last few bytes and add variablesize if needed
      if (currentbase+toread)<(OwningScanController.memregion[i].BaseAddress+OwningScanController.memregion[i].MemorySize-variablesize) then
        inc(toread, variablesize-1);



      if toread>0 then //temp bugfix to find the real bug (what causes it?)
      repeat
        size:=toread;
        if (size>buffersize) then size:=buffersize;

        actualread:=0;

        if size<toread then //there's something left to scan, so I can add the variablesize to it
          ReadProcessMemory(processhandle,pointer(currentbase),memorybuffer,size+variablesize-1,actualread)
        else
          ReadProcessMemory(processhandle,pointer(currentbase),memorybuffer,size,actualread);



        firstnextscanmem(currentbase,memorybuffer,oldbuffer,actualread);

        inc(scanned,size); //for the progressbar
        dec(toread,size);
        inc(oldbuffer,size);
        inc(currentbase,size);

      until toread=0;

    end;
    flushroutine;
  finally
    virtualfree(memorybuffer,0,MEM_RELEASE);
  end;
end;

procedure TScanner.firstscan;
var i,j: integer;

    currentbase: dword;
    size: dword;
    actualread: dword;
    memorybuffer: ^byte;
    toread: dword;
    startregion: integer;
    stopregion: integer;    
begin
  //first find out where in the previousmemory of this thread starts
  try

    startregion:=_startregion; //using a variable so stack can be used, with possibility of register
    stopregion:=_stopregion;

    if scanOption<>soUnknownValue then
    begin
      //not unknown initial
      memorybuffer:=virtualAlloc(nil,maxregionsize+16,MEM_COMMIT	or MEM_TOP_DOWN	, PAGE_READWRITE);
      configurescanroutine;
    end
    else //it is a unknown initial value, so use the previousmemorybuffer instead
    begin
      memorybuffer:=pointer(dword(OwningScanController.OwningMemScan.previousMemoryBuffer)+dword(OwningScanController.memregion[startregion].startaddress)+(startaddress-OwningScanController.memregion[startregion].BaseAddress));
      variablesize:=1; //ignore
    end;



    //now save the region between startaddress and stopaddress and create own memregion list
    setlength(memregions,16);

    for i:=startregion to stopregion do
    begin
      if terminated or OwningScanController.Terminated then exit;

      if i=startregion then
      begin
        currentbase:=startaddress;
        toread:=OwningScanController.memregion[i].MemorySize-(startaddress-OwningScanController.memregion[i].BaseAddress);
      end
      else
      begin
        currentbase:=OwningScanController.memregion[i].BaseAddress;
        toread:=OwningScanController.memregion[i].MemorySize;
      end;

      if (i=stopregion) and ((currentbase+toread)>stopaddress) then
        toread:=stopaddress-currentbase;

      repeat
        //05955958
        size:=toread;

        if (size>buffersize) then size:=buffersize;

        actualread:=0;
        //variablesize:=0;
        if size<toread then
          ReadProcessMemory(processhandle,pointer(currentbase),memorybuffer,size+variablesize-1,actualread)
        else
          ReadProcessMemory(processhandle,pointer(currentbase),memorybuffer,size,actualread);

        //+variablesize for overlap, only when not unknown var

        if scanOption=soUnknownValue then
        begin
          //unknown initial value, so create a memregion for this

          memregions[memregionpos].BaseAddress:=currentbase;
          memregions[memregionpos].MemorySize:=actualread;
          memregions[memregionpos].startaddress:=memorybuffer;

          inc(memorybuffer,size);

          inc(memregionpos);
          if (memregionpos mod 16) = 0 then
            setlength(memregions,length(memregions)+16);
        end
        else
        begin
          //scan the buffer
          firstscanmem(currentbase,memorybuffer,actualread);

        end;

        currentbase:=currentbase+size;
        
        inc(scanned,size); //for the progressbar
        dec(toread,size);

        if (OnlyOne and (found>0)) then exit;
        
      until terminated or (toread=0);
    end;

    if (scanOption<>soUnknownValue) then flushroutine; //save results
  finally
    if (scanOption<>soUnknownValue) and (memorybuffer<>nil) then
      virtualfree(memorybuffer,0,MEM_RELEASE);
  end;
end;

procedure TScanner.execute;
var i: integer;
begin
  Set8087CW($133f); //disable floating point exceptions in this thread

{$ifdef crashtest}
  //while (true) do
  begin
    sleep(16000);
  end;
{$endif}

  try
    scanwriter:=TScanfilewriter.create(self,self.OwningScanController,addressfile,memoryfile);
    if scantype=stFirstScan then firstscan;
    if scantype=stNextScan then
    begin
      if useNextNextScan then
        nextnextscan
      else
        firstnextscan;
    end;

    //tell scanwriter to stop
    scanwriter.flush;

    if scanwriter.writeError then
      raise exception.Create('Disk write error:'+scanwriter.errorString);
  except
    on e: exception do
    begin
      haserror:=true;
      errorstring:='thread '+inttostr(scannernr)+':'+e.message;

      //tell all siblings to terminate, something messed up
      //and I can just do this, since the ScanController is waiting for us, and terminate is pretty much atomic
      //for i:=0 to length(OwningScanController.scanners)-1 do

      OwningScanController.Terminate;

    end;
  end;

{$ifndef standalonetrainerwithassemblerandaobscanner}
  if (variableType=vtCustom) and (length(CustomScanAllocArray)>0) then
  begin
    try
      //custom scantype, and the script has been executed successfully (CustomScanAllocArray has been filled)
      //call customepilogue
      if assigned(customepilogue) then //customepilogue isn't nil so has been set
        CustomEpilogue; //call it

      //and dealloc the memory
      autoassemble(customscanscript,false,false,false,true,CustomScanAllocArray);



    except
      on e: exception do
      begin
        if not haserror then
        begin
          haserror:=true;
          errorstring:='Custom scan cleanup error:'+e.Message;
        end else errorstring:=errorstring+#13#10+'Additional error:'+e.message;
      end;

    end;

  end;
{$endif}  

  isdone:=true;
end;

destructor TScanner.destroy;
var i: integer;
begin


  if AddressFile<>nil then //can be made nil by the scancontroller
  begin
    Addressfile.free;
    DeleteFile(CheatEngineDir+'Addresses-'+inttostr(ThreadID)+'.TMP');
  end;

  if MemoryFile<>nil then
  begin
    MemoryFile.free;
    DeleteFile(CheatEngineDir+'Memory-'+inttostr(ThreadID)+'.TMP');
  end;

  if scanwriter<>nil then
    scanwriter.free;


  if CurrentFoundBuffer<>nil then freemem(CurrentFoundBuffer);
  if SecondaryFoundBuffer<>nil then freemem(SecondaryFoundBuffer);
  if CurrentAddressBuffer<>nil then freemem(CurrentAddressBuffer);
  if SecondaryAddressBuffer<>nil then freemem(SecondaryAddressBuffer);

{$ifndef standalonetrainerwithassemblerandaobscanner}
  if firstscanhandler<>nil then firstscanhandler.free;
{$endif}

  inherited destroy;
end;

constructor TScanner.create(suspended: boolean);
begin
  inherited create(true); //do create the thread, I need the threadid

  AddressFilename:=CheatEngineDir+'Addresses-'+inttostr(ThreadID)+'.TMP';
  MemoryFilename:=CheatEngineDir+'Memory-'+inttostr(ThreadID)+'.TMP';
  AddressFile:=TFileStream.Create(AddressFilename,fmCreate or fmSharedenynone);
  MemoryFile:=TFileStream.Create(MemoryFilename,fmCreate or fmSharedenynone);

  Priority:=cefuncproc.scanpriority;

  if not suspended then resume;   //would be stupid, but ok...
end;

//===============TScanController===============//

procedure TScanController.updategui;
var totaladdressestoscan, currentlyscanned: dword;
begin
  //runs in mainthread
  if OwningMemScan.progressbar<>nil then
  begin
    OwningMemScan.progressbar.Position:=OwningMemScan.GetProgress(totaladdressestoscan,currentlyscanned);
{$ifndef standalonetrainerwithassemblerandaobscanner}
    SetProgressValue(OwningMemScan.progressbar.Position, OwningMemScan.progressbar.Max);
{$endif}    
  end;
end;

procedure TScanController.errorpopup;
begin
  messagedlg(errorstring,mtError,[mbok],0);
end;

procedure TScanController.fillVariableAndFastScanAlignSize;
var s: string;
begin
  case variableType of
    vtByte:
    begin
      fastscanalignsize:=1;
      variablesize:=1;
    end;

    vtWord:
    begin
      fastscanalignsize:=2;
      variablesize:=2;
    end;

    vtDWord:
    begin
      fastscanalignsize:=4;
      variablesize:=4;
    end;

    vtQWord:
    begin
      fastscanalignsize:=4;
      variablesize:=8;
    end;

    vtSingle:
    begin
      fastscanalignsize:=4;
      variablesize:=4;
    end;

    vtDouble:
    begin
      fastscanalignsize:=4;
      variablesize:=8;
    end;

    vtByteArray:
    begin
      if scanoption<>soUnknownValue then
        variablesize:=getBytecountArrayOfByteString(scanvalue1)
      else
        variablesize:=1;

      fastscanalignsize:=1;

      self.OwningMemScan.arrayLength:=variablesize;
    end;

    vtBinary:
    begin
      if scanoption<>soUnknownValue then
        variablesize:=getBytecountBinaryString(scanvalue1,binaryStringAsDecimal)
      else
        variablesize:=1;

      fastscanalignsize:=1;

      //store some info for lookup
      if binaryStringAsDecimal then //first convert do binarystring
        s:=inttobin(strtoint(scanvalue1))
      else
        s:=trim(scanvalue1);

      self.OwningMemScan.binaryLength:=length(s);
    end;

    vtString:
    begin
      if scanoption<>soUnknownValue then
      begin
        variablesize:=length(scanvalue1);
        if unicode then variablesize:=variablesize*2;
      end
      else variablesize:=1;
      
      fastscanalignsize:=1;

      self.OwningMemScan.stringUnicode:=unicode;
      self.OwningMemScan.stringLength:=length(scanvalue1);
    end;

    vtAll:
    begin
      variablesize:=8;
      fastscanalignsize:=1;
    end;


    vtCustom:
    begin
      variablesize:=1; //customtypecallback(scanvalue1);
      fastscanalignsize:=1;
    end;
  end;


  OutputDebugString('fillVariableAndFastScanAlignSize:');

  OutputDebugString(format('variableType=%d',[integer(variableType)]));
  OutputDebugString(format('variablesize=%d',[variablesize]));
  OutputDebugString(format('fastscanalignsize=%d',[fastscanalignsize]));
end;


procedure TScanController.NextNextScan;
{
NextNextScan will read results of the previous scan, and pass it off to scanner threads
}
var 
    AddressFile,MemoryFile: TFileStream;
    blocksize: integer;
    i: integer;

    currententry: integer;
    datatype: string[6];
begin
  threadcount:=getcpucount;

  
  //read the results and split up
  AddressFile:=TFileStream.Create(CheatEngineDir+'Addresses.TMP',fmOpenRead or fmShareDenyNone);
  try
    if variableType in [vtbinary,vtall] then //it uses a specific TBitAddress instead of a dword
      totalAddresses:=(addressfile.size-7) div 8
    else
      totalAddresses:=(addressfile.size-7) div 4;

      //split up into blocks
    //if totaladdresses>0 then
    begin
      blocksize:=totaladdresses div threadcount;


      if (blocksize=0) and (totaladdresses>0) then blocksize:=1; //in case of 4 threads and totaladdresses is 3 or less

      scannersCS.Enter; //block access by the mainthread on the scanners object, could scanner[14] has not yet been created when doing a progress request
      try
        setlength(scanners,threadcount);
        //setup the scanner threads

        currententry:=0;
        for i:=0 to threadcount-1 do
        begin
          scanners[i]:=tscanner.Create(true);
          scanners[i].scannernr:=i;
          scanners[i].OwningScanController:=self;

          scanners[i].startentry:=currententry;

          if i=threadcount-1 then
            scanners[i].stopentry:=totalAddresses-1
          else
            scanners[i].stopentry:=currententry+blocksize;

          if scanners[i].stopentry>=totaladdresses then
            scanners[i].stopentry:=totalAddresses-1;

          currententry:=scanners[i].stopentry+1; //next thread will start at the next one

          scanners[i].scanType:=scanType; //stNextScan obviously
          scanners[i].scanoption:=scanoption;
          scanners[i].variableType:=VariableType;
          scanners[i].roundingtype:=roundingtype;
          scanners[i].percentage:=percentage;
          scanners[i].fastscan:=fastscan;
          scanners[i].scanValue1:=scanvalue1; //usual scanvalue
          scanners[i].scanValue2:=scanValue2; //2nd value for between scan
          scanners[i].readonly:=readonly;
          scanners[i].unicode:=unicode;
          scanners[i].caseSensitive:=caseSensitive;
          scanners[i].hexadecimal:=hexadecimal;
          scanners[i].binaryStringAsDecimal:=binaryStringAsDecimal;

          scanners[i].fastscanalignsize:=fastscanalignsize;
          scanners[i].variablesize:=variablesize;
          scanners[i].useNextNextscan:=true; //address result scan so nextnextscan
          scanners[i].customscanscript:=customscanscript;
          scanners[i].customscantype:=customscantype;

          if i=0 then //first thread gets the header part
          begin
            datatype:='NORMAL';
            scanners[i].AddressFile.WriteBuffer(datatype,sizeof(datatype));
          end;
        end;


      finally
        scannersCS.leave;
      end;


      for i:=0 to length(scanners)-1 do
        scanners[i].Resume;


      OwningMemScan.found:=0;
      //and now we wait
      for i:=0 to threadcount-1 do
      begin
        while not (terminated or scanners[i].isdone) do
        begin
          WaitForSingleObject(scanners[i].Handle,25); //25ms, an eternity for a cpu
          if OwningMemScan.progressbar<>nil then
            synchronize(updategui);
        end;

        //If terminated then stop the scanner thread and wait for it to finish
        if terminated then
        begin
          scanners[i].Terminate;
          scanners[i].WaitFor;
        end;

        if scanners[i].haserror then
        begin
          haserror:=true;
          errorstring:=scanners[i].errorstring;
          break;
        end;

        inc(OwningMemScan.found,scanners[i].totalfound);
      end;

      if OwningMemScan.progressbar<>nil then
        synchronize(updategui);
        
      if haserror then
      begin
        //synchronize(errorpopup);
        exit;
      end;

    end;

  finally
    addressfile.free; //release the file so it can be overwritten
  end;

  savescannerresults:=true;

end;


procedure TScanController.FirstNextScan;
{
FirstNextScan will read first scan regions, and pass it off to scanner threads
}
var

  i,j: integer;
  totalProcessMemorySize: dword;
  blocksize: dword;
  leftfromprevious: dword;
  offsetincurrentregion: dword;
  
  currentblocksize: dword;
  memoryfile,addressfile: tfilestream;
  datatype: string[6];
begin
  threadcount:=GetCPUCount;
  totalProcessMemorySize:=0;

  memregion:=OwningMemscan.memRegion;
  memregionpos:=OwningMemscan.memRegionPos;


  {
  read the addressfile and split it up into chunks for the scanner threads
  important variables:
  OwningMemScan.previousMemoryBuffer
  OwningMemscan.memRegion
  OwningMemscan.memRegionPos
  }
  //find total memoryammount
  for i:=0 to memRegionPos do
    inc(totalProcessMemorySize, memRegion[i].MemorySize);

  totalAddresses:=totalProcessMemorySize;

  //now split up into workloads
  Blocksize:=totalProcessMemorySize div threadcount;
  Blocksize:=blocksize-(blocksize mod 4096); //lastblock gets the missing bytes


  //spawn the thread, but don't run till configured
  scannersCS.Enter; //block access by the mainthread on the scanners object, could scanner[14] has not yet been created when doing a progress request
  try
    setlength(scanners,threadcount);
    j:=0; //start at memregion 0
    leftfromprevious:=0;
    offsetincurrentregion:=0;

    for i:=0 to threadcount-1 do
    begin
      scanners[i]:=tscanner.Create(true);
      scanners[i].scannernr:=i;
      scanners[i].OwningScanController:=self;

      scanners[i]._startregion:=j;
      scanners[i].startaddress:=memRegion[j].BaseAddress+offsetincurrentregion;

      scanners[i].maxregionsize:=0;

      if i=(threadcount-1) then
      begin
        //this is the last scanner
        scanners[i].stopaddress:=stopaddress;  //let it go till the end
        scanners[i]._stopregion:=memregionpos-1;

        if scanOption<>soUnknownValue then
        begin
          //define maxregionsize
          while j<memregionpos do
          begin
            if scanners[i].maxregionsize<memregion[j].MemorySize then
              scanners[i].maxregionsize:=memregion[j].MemorySize;
              
            inc(j);
          end;
        end;
      end
      else
      begin
        currentblocksize:=0;
        inc(currentblocksize,memregion[j].MemorySize-offsetincurrentregion);
        inc(j);

        scanners[i].maxregionsize:=currentblocksize; 

        //find the maximum blocksize
        while (currentblocksize<blocksize) and (j<memregionpos) do
        begin
          if scanners[i].maxregionsize<memregion[j].MemorySize then
            scanners[i].maxregionsize:=memregion[j].MemorySize;

          inc(currentblocksize,memregion[j].MemorySize);
          inc(j);
        end;
        dec(j);

        if scanners[i].maxregionsize=0 then //(currentblocksize<blocksize)
          scanners[i].maxregionsize:=memregion[j].MemorySize;

        scanners[i]._stopregion:=j;
        scanners[i].stopaddress:=memregion[j].BaseAddress+memregion[j].MemorySize;

        leftfromprevious:=currentblocksize-blocksize;
        dec(scanners[i].stopaddress,leftfromprevious);

        if leftfromprevious=0 then
        begin
          inc(j); //nothing left in this region
          offsetincurrentregion:=0;
        end else offsetincurrentregion:=memregion[j].MemorySize-leftfromprevious;



      end;

      if scanners[i].maxregionsize>buffersize then
        scanners[i].maxregionsize:=buffersize;

      //now configure the scanner thread with the same info this thread got, with some extra info

      scanners[i].scanType:=scanType; //stNextScan obviously
      scanners[i].scanoption:=scanoption;
      scanners[i].variableType:=VariableType;
      scanners[i].roundingtype:=roundingtype;
      scanners[i].percentage:=percentage;
      scanners[i].fastscan:=fastscan;
      scanners[i].scanValue1:=scanvalue1; //usual scanvalue
      scanners[i].scanValue2:=scanValue2; //2nd value for between scan
      scanners[i].readonly:=readonly;
      scanners[i].unicode:=unicode;
      scanners[i].caseSensitive:=caseSensitive;
      scanners[i].hexadecimal:=hexadecimal;
      scanners[i].binaryStringAsDecimal:=binaryStringAsDecimal;

      scanners[i].fastscanalignsize:=fastscanalignsize;
      scanners[i].variablesize:=variablesize;
      scanners[i].useNextNextscan:=false; //region scan so firstnextscan
      scanners[i].customscanscript:=customscanscript;
      scanners[i].customscantype:=customscantype;

      if i=0 then //first thread gets the header part
      begin
        datatype:='NORMAL';
        scanners[i].AddressFile.WriteBuffer(datatype,sizeof(datatype));
      end;

    end;
  finally
    scannersCS.Leave;
  end;

  //all threads are created, so start them
  for i:=0 to length(scanners)-1 do
    scanners[i].Resume;


  savescannerresults:=true;


  OwningMemScan.found:=0;
  //and now we wait
  for i:=0 to threadcount-1 do
  begin
    while not (terminated or scanners[i].isdone) do
    begin
      WaitForSingleObject(scanners[i].Handle,25); //25ms, an eternity for a cpu
      if OwningMemScan.progressbar<>nil then
        synchronize(updategui);
    end;

    //If terminated then stop the scanner thread and wait for it to finish
    if terminated then
    begin
      scanners[i].Terminate;
      scanners[i].WaitFor;
    end;


    //scanners[i].WaitFor; //if the mainthread has to cancel, it has to tell the child scanners to terminate instead
    if scanners[i].haserror then
    begin
      haserror:=true;
      errorstring:=scanners[i].errorstring;
      break;
    end;

    inc(OwningMemScan.found,scanners[i].totalfound);
  end;

  if OwningMemScan.progressbar<>nil then
    synchronize(updategui);
    
  if haserror then
  begin
    //synchronize(errorpopup);
    exit;
  end;

  //scan is successfull.



  //now clean up some mem, it's not needed anymore
  if OwningMemScan.previousMemoryBuffer<>nil then
  begin
    virtualfree(OwningMemScan.previousMemoryBuffer,0,MEM_RELEASE);
    OwningMemscan.previousMemoryBuffer:=nil;
  end;
end;


procedure TScanController.nextScan;
var AddressFile: TFilestream;
    datatype: string[6];
begin
  //open the address file and determine if it's a region scan or result scan
  AddressFile:=TFileStream.Create(CheatEngineDir+'Addresses.TMP',fmOpenRead or fmSharedenynone);
  try
    Addressfile.ReadBuffer(datatype,sizeof(datatype));
  finally
    addressFile.free;
  end;

  if datatype='REGION' then
    FirstNextScan
  else
    NextNextScan;
end;

procedure TScanController.firstScan;
{
first scan will gather the memory regions, open the files, and spawn scanners
}
var
  currentBaseAddress: dword;
  mbi : TMemoryBasicInformation;

  i,j: integer;

  Blocksize: dword;
  currentblocksize: dword;
  totalProcessMemorySize: dword;
  leftfromprevious: dword;
  offsetincurrentregion: dword;



  datatype: string[6];
begin
  OutputDebugString('TScanController.firstScan');
  if OnlyOne then
    threadcount:=1
  else
    threadcount:=GetCPUCount;
    
  totalProcessMemorySize:=0;


  OutputDebugString(format('threadcount=%d',[threadcount]));
  {
  ScanController plan:
  spawn idle scanner threads , ammount=maxthreadcount in settings
  enumerate all regions and split up into jobs for the threads
  if scanoption=soUnknownValue make a buffer  big enough to hold all the memory, and give the threads the startbase where in the buffer their first region will start
  start scanner threads
  }

  //determine the size in bytes for this variable. If one is provided.
  //also fill in the fastscan alignment as well





  //else it's ignored and never used


  setlength(memRegion,16);
  memRegionPos:=0;


  if OnlyOne then //don't go back, but forward
  begin
    if (startaddress mod 8)>0 then //align on a 8 byte base
     startaddress:=startaddress-(startaddress mod 8)+8;
  end
  else
  begin
    if (startaddress mod 8)>0 then //align on a 8 byte base
     startaddress:=startaddress-(startaddress mod 8);
  end;

  OutputDebugString(format('startaddress=%x',[startaddress]));

  OutputDebugString('Finding out memory size');
  currentBaseAddress:=startaddress;
  while (Virtualqueryex(processhandle,pointer(currentBaseAddress),mbi,sizeof(mbi))<>0) and (currentBaseAddress<stopaddress) and ((currentBaseAddress+mbi.RegionSize)>currentBaseAddress) do   //last check is done to see if it wasn't a 64-bit overflow.
  begin
    if (not (not scan_mem_private and (mbi.type_9=mem_private))) and (not (not scan_mem_image and (mbi.type_9=mem_image))) and (not (not scan_mem_mapped and (mbi.type_9=mem_mapped))) and (mbi.State=mem_commit) and ((mbi.Protect and page_guard)=0) and ((mbi.protect and page_noaccess)=0) then  //look if it is commited
    begin
      if dword(mbi.BaseAddress)<startaddress then
      begin
        dec(mbi.RegionSize, startaddress-dword(mbi.BaseAddress));
        mbi.BaseAddress:=pointer(startaddress);
      end;
      
      if dword(mbi.BaseAddress)+mbi.RegionSize>=stopaddress then
        mbi.RegionSize:=stopaddress-dword(mbi.BaseAddress);

      if //no cache check
         (Skip_PAGE_NOCACHE and ((mbi.AllocationProtect and PAGE_NOCACHE)=PAGE_NOCACHE))
         or
         //no readonly check
         ((not readonly) and (not ((((mbi.AllocationProtect) and (page_readonly or page_execute_read))=0) and
           (((mbi.Protect) and (page_readonly or PAGE_EXECUTE_READ))=0))))
       then
      begin
        //skip it
        currentBaseAddress:=dword(mbi.BaseAddress)+mbi.RegionSize;
        continue;
      end;

      //still here, so valid
      try
        if memRegionPos>0 then
        begin
          //check if it can be appended to the previous region
          if memRegion[memRegionPos-1].BaseAddress+memRegion[memRegionPos].MemorySize=dword(mbi.baseaddress) then //yes, append
          begin
            //yes, so append
            memRegion[memRegionPos-1].MemorySize:=memRegion[memRegionPos-1].MemorySize+mbi.RegionSize;
            continue;              
          end;
        end;

        //still here, so a new region
        memRegion[memRegionPos].BaseAddress:=dword(mbi.baseaddress);  //just remember this location
        memRegion[memRegionPos].MemorySize:=mbi.RegionSize;
        memRegion[memRegionPos].startaddress:=pointer(totalProcessMemorySize); //starts from 0, for unknown scans

        inc(memRegionPos);
        if (memRegionPos mod 16)=0 then //add another 16 to it
          setlength(memRegion,length(memRegion)+16);

      finally
        inc(totalProcessMemorySize,mbi.RegionSize); //add this size to the total

      end;
    end;


    currentBaseAddress:=dword(mbi.baseaddress)+mbi.RegionSize;
  end;

  OutputDebugString(format('memRegionPos=%d',[memRegionPos]));
  for i:=0 to memRegionPos-1 do
    OutputDebugString(format('i: %d B=%x S=%x SA=%p',[i, memRegion[i].BaseAddress, memRegion[i].MemorySize, memRegion[i].startaddress]));

  OutputDebugString(format('totalProcessMemorySize=%x (%d)',[totalProcessMemorySize, totalProcessMemorySize]));



  totalAddresses:=totalProcessMemorySize;

  if memRegionPos=0 then raise exception.Create('No readable memory found');


  //if soUnknown, make a buffer where it can store all the 'previous' memory
  if scanOption=soUnknownValue then
  begin
    OutputDebugString('scanOption=soUnknownValue');

    //extra check to make sure the previous scan was cleared
    if OwningMemScan.previousMemoryBuffer<>nil then virtualfree(OwningMemScan.previousMemoryBuffer,0,MEM_RELEASE);

    OutputDebugString(format('Allocating %xKB for previousMemoryBuffer',[totalProcessMemorySize div 1024]));
    OwningMemScan.previousMemoryBuffer:=VirtualAlloc(nil,totalProcessMemorySize, MEM_COMMIT	or MEM_TOP_DOWN, PAGE_READWRITE); //top down to try to prevent memory fragmentation
    if OwningMemScan.previousMemoryBuffer=nil then
      raise exception.Create('Failure allocating memory for copy');

    OutputDebugString(format('Allocated at %p',[OwningMemScan.previousMemoryBuffer]));
  end;

  //split up into seperate workloads
  OutputDebugString(format('Splitting up the workload between %d threads',[threadcount]));
  Blocksize:=totalProcessMemorySize div threadcount;
  if (Blocksize mod 4096) > 0 then
    Blocksize:=blocksize-(blocksize mod 4096); //lastblock gets the missing bytes

  OutputDebugString(format('Blocksize = %x',[Blocksize]));



  scannersCS.Enter; //block access by the mainthread on the scanners object, could scanner[14] has not yet been created when doing a progress request
  try
    setlength(scanners,threadcount);
    j:=0; //start at memregion 0
    leftfromprevious:=0;
    offsetincurrentregion:=0;

    for i:=0 to threadcount-1 do
    begin
      OutputDebugString(format('Creating scanner %d',[i]));
      scanners[i]:=tscanner.Create(true);
      scanners[i].scannernr:=i;
      scanners[i].OwningScanController:=self;


      scanners[i]._startregion:=j;
      scanners[i].startaddress:=memRegion[j].BaseAddress+offsetincurrentregion;
      scanners[i].maxregionsize:=0;

      if i=(threadcount-1) then
      begin
        //if it's the last thread, just give it what's left
        scanners[i].stopaddress:=stopaddress;
        scanners[i]._stopregion:=memregionpos-1;

        //define maxregionsize
        if scanOption<>soUnknownValue then
        begin
          //define maxregionsize , go from current till end (since it'll scan averything that's left)
          while j<memregionpos do
          begin
            if scanners[i].maxregionsize<memregion[j].MemorySize then
              scanners[i].maxregionsize:=memregion[j].MemorySize;

            inc(j);
          end;
        end;
      end
      else
      begin
        //not the last thread
        currentblocksize:=0;
        inc(currentblocksize,memregion[j].MemorySize-offsetincurrentregion);
        inc(j);

        scanners[i].maxregionsize:=currentblocksize;        

        while (currentblocksize<blocksize) and (j<memregionpos) do
        begin
          if scanOption<>soUnknownValue then //not a unknown initial value scan, so it doesn't need overlap
          begin
            if scanners[i].maxregionsize<memregion[j].MemorySize then
              scanners[i].maxregionsize:=memregion[j].MemorySize;
          end;

          inc(currentblocksize,memregion[j].MemorySize);
          inc(j);
        end;
        dec(j);

        if scanners[i].maxregionsize=0 then //(currentblocksize<blocksize)
          scanners[i].maxregionsize:=memregion[j].MemorySize;

        scanners[i]._stopregion:=j;
        scanners[i].stopaddress:=(memregion[j].BaseAddress+memregion[j].MemorySize);

        //take off the bytes that are too many for this block
        leftfromprevious:=currentblocksize-blocksize;
        dec(scanners[i].stopaddress,leftfromprevious);

        if leftfromprevious=0 then
        begin
          inc(j); //nothing left in this region
          offsetincurrentregion:=0;
        end else offsetincurrentregion:=memregion[j].MemorySize-leftfromprevious;


      end;
      OutputDebugString(format('startregion = %d',[scanners[i]._startregion]));
      OutputDebugString(format('stopregion = %d',[scanners[i]._stopregion]));
      OutputDebugString(format('startaddress = %x',[scanners[i].startaddress]));
      OutputDebugString(format('stopaddress = %x',[scanners[i].stopaddress]));

      OutputDebugString(format('j = %d',[j]));
      OutputDebugString(format('leftfromprevious = %x',[leftfromprevious]));
      OutputDebugString(format('offsetincurrentregion = %x',[offsetincurrentregion]));



      if scanners[i].maxregionsize>buffersize then
        scanners[i].maxregionsize:=buffersize;

      OutputDebugString(format('maxregionsize = %x',[scanners[i].maxregionsize]));
              

      //now configure the scanner thread with the same info this thread got, with some extra info
      scanners[i].scanType:=scanType; //stFirstScan obviously
      scanners[i].scanoption:=scanoption;
      scanners[i].variableType:=VariableType;
      scanners[i].roundingtype:=roundingtype;
      scanners[i].percentage:=percentage;
      scanners[i].fastscan:=fastscan;
      scanners[i].scanValue1:=scanvalue1; //usual scanvalue
      scanners[i].scanValue2:=scanValue2; //2nd value for between scan
      scanners[i].readonly:=readonly;
      scanners[i].unicode:=unicode;
      scanners[i].OnlyOne:=OnlyOne;
      scanners[i].caseSensitive:=caseSensitive;
      scanners[i].hexadecimal:=hexadecimal;
      scanners[i].binaryStringAsDecimal:=binaryStringAsDecimal;

      scanners[i].fastscanalignsize:=fastscanalignsize;
      scanners[i].variablesize:=variablesize;
      scanners[i].customscanscript:=customscanscript;
      scanners[i].customscantype:=customscantype;

      if i=0 then //first thread gets the header part
      begin
        if scanoption=soUnknownValue then
          datatype:='REGION'
        else
          datatype:='NORMAL';

        scanners[i].AddressFile.WriteBuffer(datatype,sizeof(datatype));
      end;

    end;
  finally
    scannersCS.Leave;
  end;

  //all threads are created, so start them
  for i:=0 to length(scanners)-1 do
    scanners[i].Resume;

  //prepare the result files
  try

    OwningMemScan.found:=0;
    //and now we wait
    for i:=0 to threadcount-1 do
    begin
      while not (terminated or scanners[i].isdone) do
      begin
        WaitForSingleObject(scanners[i].Handle,25); //25ms, an eternity for a cpu
        if OwningMemScan.progressbar<>nil then
          synchronize(updategui);
      end;

      //If terminated then stop the scanner thread and wait for it to finish
      if terminated then
      begin
        scanners[i].Terminate;
        scanners[i].WaitFor;
      end;

      if scanners[i].haserror then
      begin
        OwningMemScan.found:=0;
        haserror:=true;
        errorstring:=scanners[i].errorstring;
        break;
      end;
      inc(OwningMemScan.found,scanners[i].totalfound);
    end;

    if OnlyOne then
    begin
      if (scanners[0].found) or (scanners[0].totalfound)>0 then
      begin
        FoundSomething:=true;
        AddressFound:=scanners[0].AddressFound;

      end;
    end;

    if OwningMemScan.progressbar<>nil then
      synchronize(updategui);
      
    if haserror then
    begin
      OwningMemScan.found:=0;
      //synchronize(errorpopup);
      exit;
    end;

    //all threads are done
    //combine all results and write them too the AddressFile and MemoryFile



    if scanOption=soUnknownValue then
    begin
      //read the scanner memregions and adapt this memregion to it

      //first find out how many regions it got
      memRegionPos:=0;
      for i:=0 to threadcount-1 do
        inc(memRegionPos,scanners[i].memRegionPos);

      setlength(OwningMemScan.memRegion,memRegionPos); //setting the size accordingly (max, can end up smaller due to appending)

      OwningMemScan.memRegionPos:=0;
      for i:=0 to threadcount-1 do
      begin
        for j:=0 to scanners[i].memRegionPos-1 do
        begin
          inc(OwningMemScan.found,scanners[i].memregions[j].MemorySize);
          
          if OwningMemScan.memregionpos>0 then
          begin
            if OwningMemScan.memregion[OwningMemScan.memregionpos-1].BaseAddress+OwningMemScan.memregion[OwningMemScan.memregionpos-1].MemorySize=scanners[i].memregions[j].BaseAddress then
            begin
              //append
              inc(OwningMemScan.memregion[OwningMemScan.memregionpos-1].MemorySize,scanners[i].memregions[j].MemorySize);
              continue;
            end;
          end;

          //new one
          OwningMemScan.memregion[OwningMemScan.memregionpos]:=scanners[i].memregions[j];
          inc(OwningMemScan.memregionpos);
        end;

      end;

      if fastscan then //divide by alignsize of fastscan
        OwningMemScan.found:=OwningMemScan.found div fastscanalignsize;

      savescannerresults:=true;
    end
    else
    begin
      savescannerresults:=true; 
    end;
    
  finally
  end;



end;

procedure TScanController.execute;
var err: dword;
    i: integer;
    oldpos,oldmempos: integer;
begin
  OutputDebugString('TScanController.execute');
  try

    //check what it is, and call first/next/nextnext- scan
    err:=0;
    errorstring:='';


    try
      fillVariableAndFastScanAlignSize;
      if scantype=stFirstScan then firstscan;
      if scantype=stNextScan then nextscan;
      OutputDebugString('No exception on controller');
    except
      on e: exception do
      begin
        OutputDebugString(pchar('controller exception happened:'+e.message));
        haserror:=true;
        errorstring:='controller:'+e.message;
      end;
    end;


    if OnlyOne then savescannerresults:=false; //DO NOT INTERFERE
    
    if savescannerresults then //prepare saving. Set the filesize
    begin
      try
        OutputDebugString('ScanController: creating undo files');
        freeandnil(scanners[0].Addressfile);
        freeandnil(scanners[0].Memoryfile);

        //addresses
        deletefile(CheatEngineDir+'Addresses.UNDO');
        renamefile(CheatEngineDir+'Addresses.TMP',CheatEngineDir+'Addresses.UNDO');
        renamefile(scanners[0].Addressfilename, CheatEngineDir+'Addresses.TMP');

        //memory
        deletefile(CheatEngineDir+'Memory.UNDO');
        renamefile(CheatEngineDir+'Memory.TMP',CheatEngineDir+'Memory.UNDO');
        renamefile(scanners[0].Memoryfilename, CheatEngineDir+'Memory.TMP');
        try
          AddressFile:=TFileStream.Create(CheatEngineDir+'Addresses.TMP',fmOpenWrite or fmShareDenyNone);
          MemoryFile:=TFileStream.Create(CheatEngineDir+'Memory.TMP',fmOpenWrite or fmsharedenynone);
        except
          outputdebugstring('Scancontroller: Error when while loading result');
          haserror:=true;
          errorstring:='Error while loading results';
          exit;
        end;

        oldpos:=addressfile.Size;
        oldmempos:=memoryfile.size;

        for i:=1 to threadcount-1 do
          AddressFile.size:=AddressFile.size+scanners[i].Addressfile.Size;

        outputdebugstring(format('ScanController: Have set AddressFile.size to %d',[AddressFile.size]));
      except
        on e: exception do
        begin
          OutputDebugString(pchar('Disk Write Error:'+e.message));
          haserror:=true;
          errorstring:='controller:Cleanup:ResultsPrepare:'+e.message;
        end;
      end;
    end;

    if haserror then
    begin
      err:=1;
      outputdebugstring('There was an error during the scan:'+errorstring);
    end;

    //send message saying it's done
    isdone:=true;
    if notifywindow<>0 then
      postMessage(notifywindow,notifymessage,err,0);

    isdoneevent.setevent;

    try
      if savescannerresults and (addressfile<>nil) then //now actually save the scanner results
      begin
        //AddressFile should already have been created with the correct datatype and opened as denynone
        AddressFile.Seek(oldpos,soFromBeginning);
        Memoryfile.seek(oldmempos,soFromBeginning);

        //save the exact results, and copy it to the AddressesFirst.tmp and Memoryfirst.tmp files
        for i:=1 to length(scanners)-1 do
        begin
          outputdebugstring(format('ScanController: Writing results from scanner %d',[i]));
          addressfile.CopyFrom(scanners[i].Addressfile,0);
          Memoryfile.CopyFrom(scanners[i].MemoryFile,0);
        end;
      end;
    except
      on e: exception do
      begin
        OutputDebugString(pchar('Disk Write Error:'+e.message));
        haserror:=true;
        errorstring:='controller:Cleanup:ResultsWrite:'+e.message;
      end;
    end;

{$ifndef standalonetrainerwithassemblerandaobscanner}
    try
      if scantype=stFirstScan then
      begin
        outputdebugstring('ScanController: This was a first scan, so saving the First Scan results');
        OwningMemScan.SaveFirstScanThread:=TSaveFirstScanThread.create(false,@OwningMemScan.memregion,@OwningMemScan.memregionpos, OwningMemScan.previousMemoryBuffer);
      end;
    except
      on e: exception do
      begin
        OutputDebugString(pchar('First Scan Create:'+e.message));
        haserror:=true;
        errorstring:='controller:Cleanup:Failed spawning the Save First Scan thread:'+e.message;
      end;
    end;
{$endif}    


    //clean up secondary scanner threads, their destructor will close and delete their files
    outputdebugstring('ScanController: Destroying scanner threads');
    scannersCS.enter;
    try
      outputdebugstring('ScanController: Critical section "scannersCS" aquired');
      for i:=0 to length(scanners)-1 do
      begin
        outputdebugstring(format('ScanController: Freeing scanner %d',[i]));
        scanners[i].Free;
      end;

      setlength(scanners,0);
    finally
      scannersCS.leave;
      outputdebugstring('ScanController: Critical section "scannersCS" released');
    end;


    //cleanup the files
    if addressfile<>nil then addressfile.Free;
    if MemoryFile<>nil then Memoryfile.Free;
  except
    on e: exception do
    begin
      OutputDebugString(pchar('controller exception happened:Unknown!'+e.message));
      haserror:=true;
      errorstring:='controller:Unknown!'+e.message;
    end;
  end;
end;

constructor TScanController.create(suspended: boolean);
begin
{$ifndef standalonetrainerwithassemblerandaobscanner}
  SetProgressstate(tbpsNormal);
{$endif}
  
  isdoneevent:=TEvent.create(nil,true,false,'');
  scannersCS:=TCriticalSection.Create;
  resultsaveCS:=TCriticalsection.create;
  
  inherited create(suspended);
end;

destructor TScancontroller.destroy;
begin
  scannersCS.free;
  resultsaveCS.free;
  inherited destroy;
end;


//----------------memscan--------------//
procedure TMemscan.TerminateScan(forceTermination: boolean);
var i: integer;
begin
  if scancontroller<>nil then
  begin
    if not forceTermination then
    begin
      scanController.Terminate
    end
    else
    begin
      //scancontroller.forceTerminate:=true;


      for i:=0 to length(scanController.scanners)-1 do
      begin
        try
          TerminateThread(scanController.scanners[i].Handle,$dead);
          scanController.scanners[i].Free;
        except

        end;
      end;

      setlength(scanController.scanners,0);
      try
        TerminateThread(scancontroller.Handle,$dead);
        freeandnil(scancontroller);
      except

      end;

      if notifywindow<>0 then
        PostMessage(notifywindow, notifymessage,0,0);

      scanController.isDoneEvent.SetEvent;
    end;
  end
  else
    raise exception.Create('For some reason the scanController does not exist');
  //and now the caller has to wait
end;



function TMemscan.GetErrorString: string;
begin
  result:='';
  if scancontroller<>nil then
  begin
    scancontroller.WaitFor;
    if scancontroller.haserror then
      result:=scancontroller.errorstring;
  end;

end;

procedure TMemscan.waittilldone;
begin
  if scancontroller<>nil then
    scancontroller.isdoneEvent.WaitFor(INFINITE);
end;

function TMemscan.GetOnlyOneResult(var address: dword):boolean;
begin
  if self.scanController<>nil then
  begin
    result:=scancontroller.FoundSomething;
    address:=scancontroller.AddressFound;
  end;
end;

function TMemscan.GetProgress(var totaladdressestoscan:dword; var currentlyscanned: dword):integer;
{returns a value between 1 and 1000 representing how far the scan is}
var i: integer;
begin
  //Take care of memory
  if self.scanController<>nil then
  begin
    totaladdressestoscan:=self.scanController.totalAddresses;
    currentlyscanned:=0;
    scanController.scannersCS.enter;
    try
      for i:=0 to length(self.scanController.scanners)-1 do
        inc(currentlyscanned,self.scanController.scanners[i].scanned);

    finally
      scanController.scannersCS.Leave;
    end;
    
    result:=trunc((currentlyscanned / totaladdressestoscan) * 1000);
  end
  else
  begin
    result:=0;
    totaladdressestoscan:=0;
    currentlyscanned:=0;
  end;
end;

function TMemscan.GetFoundCount: uint64;
begin
  result:=found;
end;

function TMemscan.Getcustomvariablesize: integer;
{
Rturns the veriable size of the custom type
only valid after having done a scan, not before
}
begin
  result:=customvariablesize;
end;

function TMemscan.Getbinarysize: int64;
begin
  case self.currentVariableType of
    vtByte:      result:=8;
    vtWord:      result:=16;
    vtDWord:     result:=32;
    vtQWord:     result:=64;
    vtSingle:    result:=32;
    vtDouble:    result:=64;
    vtAll:       result:=64;
    vtString:    if stringUnicode then result:=16*stringLength else result:=8*stringLength;
    vtBinary:    result:=binaryLength;
    vtByteArray: result:=arrayLength*8;
    else result:=8;
  end;
end;

procedure TMemscan.newscan;
begin
  if scanController<>nil then
  begin
    scanController.terminate;
    scanController.WaitFor;
    FreeAndNil(scanController);
  end;

{$ifndef standalonetrainerwithassemblerandaobscanner}
  if SaveFirstScanThread<>nil then
  begin
    SaveFirstScanThread.Terminate;
    SaveFirstScanThread.WaitFor; //wait till it's done
    freeandnil(SaveFirstScanThread);
  end;
{$endif}  

  if previousMemoryBuffer<>nil then virtualfree(previousMemoryBuffer,0,MEM_RELEASE);
  fLastscantype:=stNewScan;
end;

procedure TMemscan.NextScan(scanOption: TScanOption; roundingtype: TRoundingType; scanvalue1, scanvalue2: string; startaddress,stopaddress: dword; fastscan,readonly,hexadecimal,binaryStringAsDecimal, unicode, casesensitive, percentage: boolean; customscanscript: tstrings; customscantype: TCustomScanType);
begin

  if scanController<>nil then
  begin
    scancontroller.WaitFor; //could be it's still saving the results of the previous scan
    freeandnil(scanController);
  end;

{$ifndef standalonetrainerwithassemblerandaobscanner}
  if SaveFirstScanThread<>nil then
  begin
    SaveFirstScanThread.WaitFor; //wait till it's done
    freeandnil(SaveFirstScanThread);
  end;
{$endif}  

  scanController:=TscanController.Create(true);
  scanController.OwningMemScan:=self;
  scanController.scantype:=stNextScan;
  scanController.scanOption:=scanOption;
  scanController.variableType:=CurrentVariableType;
  scanController.roundingtype:=roundingtype;
  scanController.percentage:=percentage;
  scanController.fastscan:=fastscan;
  scanController.scanValue1:=scanvalue1; //usual scanvalue
  scanController.scanValue2:=scanValue2; //2nd value for between scan
  scanController.readonly:=readonly;
  scanController.startaddress:=startaddress;
  scanController.stopaddress:=stopaddress;

  scancontroller.hexadecimal:=hexadecimal;
  scancontroller.binaryStringAsDecimal:=binaryStringAsDecimal;
  scancontroller.unicode:=unicode;
  scancontroller.casesensitive:=casesensitive;
  scancontroller.notifywindow:=notifywindow;
  scancontroller.notifymessage:=notifymessage;
  
  scancontroller.CustomScanScript:=CustomScanScript;
  scanController.CustomScanType:=CustomScanType;

  fLastscantype:=stNextScan;
  scanController.Resume;

end;

procedure TMemscan.firstscan(scanOption: TScanOption; VariableType: TVariableType; roundingtype: TRoundingType; scanvalue1, scanvalue2: string; startaddress,stopaddress: dword; fastscan,readonly,hexadecimal,binaryStringAsDecimal,unicode,casesensitive,percentage: boolean; customscanscript: tstrings; customscantype: TCustomScanType);
{
Spawn the controller thread and fill it with the required data
Popup the wait window, or not ?
}
begin
  if scanController<>nil then freeandnil(scanController);
{$ifndef standalonetrainerwithassemblerandaobscanner}
  if SaveFirstScanThread<>nil then
  begin
    SaveFirstScanThread.Terminate; //it should quit, saving took to long and the user already started a new one
    SaveFirstScanThread.WaitFor; //wait till it has fully terminated
    freeandnil(SaveFirstScanThread);
  end;
{$endif}  

  currentVariableType:=VariableType;

  scanController:=TscanController.Create(true);
  scanController.OwningMemScan:=self;
  scanController.scantype:=stFirstScan;
  scanController.scanOption:=scanOption;
  scanController.variableType:=VariableType;
  scanController.roundingtype:=roundingtype;
  scanController.percentage:=percentage;
  scanController.fastscan:=fastscan;
  scanController.scanValue1:=scanvalue1; //usual scanvalue
  scanController.scanValue2:=scanValue2; //2nd value for between scan
  scanController.readonly:=readonly;
  scanController.startaddress:=startaddress;
  scanController.stopaddress:=stopaddress;

  scancontroller.hexadecimal:=hexadecimal;
  scancontroller.binaryStringAsDecimal:=binaryStringAsDecimal;
  scancontroller.unicode:=unicode;
  scancontroller.casesensitive:=casesensitive;
  scancontroller.notifywindow:=notifywindow;
  scancontroller.notifymessage:=notifymessage;

  scancontroller.CustomScanScript:=CustomScanScript;
  scanController.CustomScanType:=CustomScanType;
  scanController.OnlyOne:=onlyone;

  flastscantype:=stFirstScan;
  scanController.Resume;


end;

procedure TMemscan.setScanDoneCallback(notifywindow: thandle; notifymessage: integer);
begin
  self.notifywindow:=notifywindow;
  self.notifymessage:=notifymessage;
end;

constructor TMemScan.create(progressbar: TProgressbar);
begin
  self.progressbar:=progressbar;
  customvariablesize:=1;
end;
     
destructor TMemScan.destroy;
begin
  if previousMemoryBuffer<>nil then virtualfree(previousMemoryBuffer,0,MEM_RELEASE);
  {$ifndef standalonetrainerwithassemblerandaobscanner}
  if SaveFirstScanThread<>nil then SaveFirstScanThread.Free;
  {$endif}

  inherited Destroy;
end;

end.
